@rozenite/network-activity-plugin 1.0.0-alpha.9 → 1.1.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 (113) hide show
  1. package/README.md +2 -0
  2. package/dist/App.html +2 -2
  3. package/dist/assets/{App-DoHQsY5s.css → App-BrSkOkws.css} +223 -2
  4. package/dist/assets/{App-CA1Fbh0I.js → App-Kyi7zHUX.js} +8188 -2671
  5. package/dist/react-native.cjs +4 -1
  6. package/dist/react-native.js +4 -1
  7. package/dist/rozenite.json +1 -1
  8. package/dist/src/react-native/config.d.ts +20 -0
  9. package/dist/src/react-native/http/overrides-registry.d.ts +6 -0
  10. package/dist/src/react-native/http/xhr-interceptor.d.ts +7 -1
  11. package/dist/src/react-native/sse/sse-interceptor.d.ts +2 -2
  12. package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -1
  13. package/dist/src/react-native/utils/getBlobName.d.ts +35 -0
  14. package/dist/src/react-native/utils/getFormDataEntries.d.ts +18 -0
  15. package/dist/src/shared/client.d.ts +55 -4
  16. package/dist/src/shared/sse-events.d.ts +4 -1
  17. package/dist/src/ui/components/Button.d.ts +2 -2
  18. package/dist/src/ui/components/CodeBlock.d.ts +3 -0
  19. package/dist/src/ui/components/CodeEditor.d.ts +5 -0
  20. package/dist/src/ui/components/CookieCard.d.ts +7 -0
  21. package/dist/src/ui/components/CopyRequestDropdown.d.ts +7 -0
  22. package/dist/src/ui/components/DropdownMenu.d.ts +27 -0
  23. package/dist/src/ui/components/FilterBar.d.ts +10 -0
  24. package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +1 -1
  25. package/dist/src/ui/components/KeyValueGrid.d.ts +13 -0
  26. package/dist/src/ui/components/OverrideResponse.d.ts +8 -0
  27. package/dist/src/ui/components/RequestBody.d.ts +6 -0
  28. package/dist/src/ui/components/RequestList.d.ts +9 -4
  29. package/dist/src/ui/components/ScrollArea.d.ts +3 -2
  30. package/dist/src/ui/components/Section.d.ts +8 -0
  31. package/dist/src/ui/components/Separator.d.ts +2 -1
  32. package/dist/src/ui/components/Tabs.d.ts +7 -0
  33. package/dist/src/ui/state/hooks.d.ts +4 -0
  34. package/dist/src/ui/state/model.d.ts +22 -7
  35. package/dist/src/ui/state/store.d.ts +27 -3
  36. package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +2 -0
  37. package/dist/src/ui/utils/escapeShellArg.d.ts +1 -0
  38. package/dist/src/ui/utils/generateCurlCommand.d.ts +2 -0
  39. package/dist/src/ui/utils/generateFetchCall.d.ts +2 -0
  40. package/dist/src/ui/utils/generateMultipartBody.d.ts +4 -0
  41. package/dist/src/utils/applyReactNativeRequestHeadersLogic.d.ts +7 -0
  42. package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +9 -0
  43. package/dist/src/utils/cookieParser.d.ts +6 -0
  44. package/dist/src/utils/getContentTypeMimeType.d.ts +2 -0
  45. package/dist/src/utils/getHttpHeader.d.ts +5 -0
  46. package/dist/src/utils/getHttpHeaderValueAsString.d.ts +11 -0
  47. package/dist/src/utils/getStringSizeInBytes.d.ts +1 -0
  48. package/dist/src/utils/inferContentTypeFromPostData.d.ts +2 -0
  49. package/dist/src/utils/safeStringify.d.ts +1 -0
  50. package/dist/src/utils/typeChecks.d.ts +9 -0
  51. package/dist/useNetworkActivityDevTools.cjs +337 -24
  52. package/dist/useNetworkActivityDevTools.js +338 -25
  53. package/package.json +7 -4
  54. package/react-native.ts +6 -1
  55. package/src/react-native/config.ts +43 -0
  56. package/src/react-native/http/network-inspector.ts +190 -8
  57. package/src/react-native/http/overrides-registry.ts +32 -0
  58. package/src/react-native/http/xhr-interceptor.ts +19 -2
  59. package/src/react-native/sse/sse-inspector.ts +27 -5
  60. package/src/react-native/sse/sse-interceptor.ts +26 -8
  61. package/src/react-native/useNetworkActivityDevTools.ts +86 -8
  62. package/src/react-native/utils/getBlobName.ts +45 -0
  63. package/src/react-native/utils/getFormDataEntries.ts +32 -0
  64. package/src/react-native/utils.ts +3 -3
  65. package/src/shared/client.ts +81 -4
  66. package/src/shared/sse-events.ts +4 -1
  67. package/src/ui/components/Button.tsx +1 -0
  68. package/src/ui/components/CodeBlock.tsx +19 -0
  69. package/src/ui/components/CodeEditor.tsx +26 -0
  70. package/src/ui/components/CookieCard.tsx +64 -0
  71. package/src/ui/components/CopyRequestDropdown.tsx +95 -0
  72. package/src/ui/components/DropdownMenu.tsx +206 -0
  73. package/src/ui/components/FilterBar.tsx +117 -0
  74. package/src/ui/components/Input.tsx +1 -1
  75. package/src/ui/components/JsonTree.tsx +10 -3
  76. package/src/ui/components/JsonTreeCopyableItem.tsx +14 -10
  77. package/src/ui/components/KeyValueGrid.tsx +51 -0
  78. package/src/ui/components/OverrideResponse.tsx +132 -0
  79. package/src/ui/components/RequestBody.tsx +86 -0
  80. package/src/ui/components/RequestList.tsx +74 -14
  81. package/src/ui/components/ScrollArea.tsx +1 -0
  82. package/src/ui/components/Section.tsx +46 -0
  83. package/src/ui/components/SidePanel.tsx +15 -5
  84. package/src/ui/components/Toolbar.tsx +3 -2
  85. package/src/ui/globals.css +4 -0
  86. package/src/ui/hooks/useCopyToClipboard.ts +2 -2
  87. package/src/ui/state/derived.ts +2 -0
  88. package/src/ui/state/hooks.ts +8 -0
  89. package/src/ui/state/model.ts +28 -7
  90. package/src/ui/state/store.ts +640 -500
  91. package/src/ui/tabs/CookiesTab.tsx +60 -263
  92. package/src/ui/tabs/HeadersTab.tsx +78 -89
  93. package/src/ui/tabs/RequestTab.tsx +58 -46
  94. package/src/ui/tabs/ResponseTab.tsx +98 -67
  95. package/src/ui/tabs/SSEMessagesTab.tsx +50 -39
  96. package/src/ui/utils/checkRequestBodyBinary.ts +7 -0
  97. package/src/ui/utils/escapeShellArg.ts +12 -0
  98. package/src/ui/utils/generateCurlCommand.ts +83 -0
  99. package/src/ui/utils/generateFetchCall.ts +64 -0
  100. package/src/ui/utils/generateMultipartBody.ts +19 -0
  101. package/src/ui/views/InspectorView.tsx +15 -3
  102. package/src/utils/applyReactNativeRequestHeadersLogic.ts +30 -0
  103. package/src/utils/applyReactNativeResponseHeadersLogic.ts +28 -0
  104. package/src/utils/cookieParser.ts +126 -0
  105. package/src/utils/getContentTypeMimeType.ts +17 -0
  106. package/src/utils/getHttpHeader.ts +17 -0
  107. package/src/utils/getHttpHeaderValueAsString.ts +13 -0
  108. package/src/utils/getStringSizeInBytes.ts +3 -0
  109. package/src/utils/inferContentTypeFromPostData.ts +9 -0
  110. package/src/utils/safeStringify.ts +7 -0
  111. package/src/utils/typeChecks.ts +27 -0
  112. package/dist/src/ui/utils/getHttpHeaderValue.d.ts +0 -2
  113. package/src/ui/utils/getHttpHeaderValue.ts +0 -14
@@ -1,24 +1,40 @@
1
- import { useEffect } from "react";
1
+ import { useRef, useEffect } from "react";
2
2
  import { useRozeniteDevToolsClient } from "@rozenite/plugin-bridge";
3
3
  import { createNanoEvents } from "nanoevents";
4
4
  import { Platform } from "react-native";
5
5
  import WebSocketInterceptor from "react-native/Libraries/WebSocket/WebSocketInterceptor";
6
6
  import { g as getEventSource } from "./event-source.js";
7
- function getHttpHeaderValue(headers, name) {
7
+ function safeStringify(data) {
8
+ try {
9
+ return typeof data === "string" ? data : JSON.stringify(data);
10
+ } catch {
11
+ return String(data);
12
+ }
13
+ }
14
+ function getHttpHeader(headers, name) {
8
15
  const lowerName = name.toLowerCase();
9
16
  for (const key in headers) {
10
17
  if (key.toLowerCase() === lowerName) {
11
- return headers[key];
18
+ return { value: headers[key], originalKey: key };
12
19
  }
13
20
  }
14
21
  return void 0;
15
22
  }
23
+ function getContentTypeMime(headers) {
24
+ const contentType = getHttpHeader(headers, "content-type");
25
+ if (!contentType) {
26
+ return void 0;
27
+ }
28
+ const { value } = contentType;
29
+ const actualValue = Array.isArray(value) ? value[0] : value;
30
+ return actualValue.split(";")[0].trim();
31
+ }
16
32
  const getContentType = (request) => {
17
33
  const responseHeaders = request.responseHeaders;
18
34
  const responseType = request.responseType;
19
- const contentType = getHttpHeaderValue(responseHeaders || {}, "content-type");
35
+ const contentType = getContentTypeMime(responseHeaders || {});
20
36
  if (contentType) {
21
- return contentType.split(";")[0].trim();
37
+ return contentType;
22
38
  }
23
39
  switch (responseType) {
24
40
  case "arraybuffer":
@@ -66,6 +82,28 @@ const getNetworkRequestsRegistry = () => {
66
82
  clear
67
83
  };
68
84
  };
85
+ function getBlobName(blob) {
86
+ if (typeof (blob == null ? void 0 : blob.name) === "string") {
87
+ return blob.name;
88
+ }
89
+ if ((blob == null ? void 0 : blob.data) && typeof blob.data.name === "string") {
90
+ return blob.data.name;
91
+ }
92
+ return void 0;
93
+ }
94
+ function getFormDataEntries(formData) {
95
+ if (!formData || typeof formData !== "object") {
96
+ return [];
97
+ }
98
+ if (typeof formData.entries === "function") {
99
+ return formData.entries();
100
+ }
101
+ if (Array.isArray(formData._parts)) {
102
+ return formData._parts;
103
+ }
104
+ return [];
105
+ }
106
+ const XMLHttpRequest = global.XMLHttpRequest || window.XMLHttpRequest;
69
107
  const originalXHROpen = XMLHttpRequest.prototype.open;
70
108
  const originalXHRSend = XMLHttpRequest.prototype.send;
71
109
  const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
@@ -74,6 +112,7 @@ let sendCallback;
74
112
  let requestHeaderCallback;
75
113
  let headerReceivedCallback;
76
114
  let responseCallback;
115
+ let overrideCallback;
77
116
  let isInterceptorEnabled$1 = false;
78
117
  const XHRInterceptor = {
79
118
  /**
@@ -106,6 +145,12 @@ const XHRInterceptor = {
106
145
  setRequestHeaderCallback(callback) {
107
146
  requestHeaderCallback = callback;
108
147
  },
148
+ /**
149
+ * Invoked before XMLHttpRequest.send(...) is called.
150
+ */
151
+ setOverrideCallback(callback) {
152
+ overrideCallback = callback;
153
+ },
109
154
  isInterceptorEnabled() {
110
155
  return isInterceptorEnabled$1;
111
156
  },
@@ -129,6 +174,9 @@ const XHRInterceptor = {
129
174
  if (sendCallback) {
130
175
  sendCallback(data, this);
131
176
  }
177
+ if (overrideCallback) {
178
+ overrideCallback(this);
179
+ }
132
180
  if (this.addEventListener) {
133
181
  this.addEventListener(
134
182
  "readystatechange",
@@ -189,14 +237,128 @@ const XHRInterceptor = {
189
237
  sendCallback = null;
190
238
  headerReceivedCallback = null;
191
239
  requestHeaderCallback = null;
240
+ overrideCallback = null;
241
+ }
242
+ };
243
+ const getStringSizeInBytes = (value) => {
244
+ return new TextEncoder().encode(value).length;
245
+ };
246
+ const splitSetCookieHeaderByComma = (header) => {
247
+ const regex = /(?:^|,\s)([^=;,]+=[^;]+(?:;[^,]*)*)/g;
248
+ const matches = [];
249
+ let match;
250
+ while ((match = regex.exec(header)) !== null) {
251
+ matches.push(match[1].trim());
252
+ }
253
+ return matches;
254
+ };
255
+ const applyReactNativeResponseHeadersLogic = (headers) => {
256
+ const parsedHeaders = { ...headers };
257
+ const setCookieHeader = getHttpHeader(headers, "set-cookie");
258
+ if (setCookieHeader) {
259
+ const { value, originalKey } = setCookieHeader;
260
+ const cookies = splitSetCookieHeaderByComma(value);
261
+ parsedHeaders[originalKey] = cookies.length > 0 ? cookies : value;
262
+ }
263
+ return parsedHeaders;
264
+ };
265
+ const isBlob = (value) => value instanceof Blob;
266
+ const isArrayBuffer = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
267
+ const isFormData = (value) => value instanceof FormData;
268
+ const isNullOrUndefined = (value) => value === null || value === void 0;
269
+ const createOverridesRegistry = () => {
270
+ let overrides = /* @__PURE__ */ new Map();
271
+ const setOverrides = (newOverrides) => {
272
+ overrides = new Map(newOverrides);
273
+ };
274
+ const getOverrideForUrl = (url) => {
275
+ return overrides.get(url);
276
+ };
277
+ return {
278
+ setOverrides,
279
+ getOverrideForUrl
280
+ };
281
+ };
282
+ let registryInstance = null;
283
+ const getOverridesRegistry = () => {
284
+ if (!registryInstance) {
285
+ registryInstance = createOverridesRegistry();
192
286
  }
287
+ return registryInstance;
193
288
  };
194
289
  const networkRequestsRegistry = getNetworkRequestsRegistry();
290
+ const overridesRegistry$1 = getOverridesRegistry();
291
+ const getBinaryPostData = (body) => ({
292
+ type: "binary",
293
+ value: {
294
+ size: body.size,
295
+ type: body.type,
296
+ name: getBlobName(body)
297
+ }
298
+ });
299
+ const getArrayBufferPostData = (body) => ({
300
+ type: "binary",
301
+ value: {
302
+ size: body.byteLength
303
+ }
304
+ });
305
+ const getTextPostData = (body) => ({
306
+ type: "text",
307
+ value: safeStringify(body)
308
+ });
309
+ const getFormDataPostData = (body) => ({
310
+ type: "form-data",
311
+ value: getFormDataEntries(body).reduce(
312
+ (acc, [key, value]) => {
313
+ if (isBlob(value)) {
314
+ acc[key] = getBinaryPostData(value);
315
+ } else if (isArrayBuffer(value)) {
316
+ acc[key] = getArrayBufferPostData(value);
317
+ } else {
318
+ acc[key] = getTextPostData(value);
319
+ }
320
+ return acc;
321
+ },
322
+ {}
323
+ )
324
+ });
325
+ const getRequestBody = (body) => {
326
+ if (isNullOrUndefined(body)) {
327
+ return body;
328
+ }
329
+ if (isBlob(body)) {
330
+ return getBinaryPostData(body);
331
+ }
332
+ if (isArrayBuffer(body)) {
333
+ return getArrayBufferPostData(body);
334
+ }
335
+ if (isFormData(body)) {
336
+ return getFormDataPostData(body);
337
+ }
338
+ return getTextPostData(body);
339
+ };
195
340
  const getResponseSize = (request) => {
196
- if (typeof request.response === "object") {
197
- return request.response.size;
341
+ try {
342
+ const { responseType, response } = request;
343
+ if (response === null) {
344
+ return 0;
345
+ }
346
+ if (responseType === "" || responseType === "text") {
347
+ return getStringSizeInBytes(request.responseText);
348
+ }
349
+ if (responseType === "json") {
350
+ return getStringSizeInBytes(safeStringify(response));
351
+ }
352
+ if (responseType === "blob") {
353
+ return response.size;
354
+ }
355
+ if (responseType === "arraybuffer") {
356
+ return response.byteLength;
357
+ }
358
+ return 0;
359
+ } catch {
360
+ return null;
198
361
  }
199
- return request.response.length || 0;
200
362
  };
201
363
  const getResponseBody = async (request) => {
202
364
  const responseType = request.responseType;
@@ -215,6 +377,9 @@ const getResponseBody = async (request) => {
215
377
  });
216
378
  }
217
379
  }
380
+ if (responseType === "json") {
381
+ return safeStringify(request.response);
382
+ }
218
383
  return null;
219
384
  };
220
385
  const getInitiatorFromStack = () => {
@@ -256,11 +421,20 @@ const getNetworkInspector = (pluginClient) => {
256
421
  url: request._url,
257
422
  method: request._method,
258
423
  headers: request._headers,
259
- postData: data
424
+ postData: getRequestBody(data)
260
425
  },
261
426
  type: "XHR",
262
427
  initiator
263
428
  });
429
+ request.addEventListener("progress", (event) => {
430
+ pluginClient.send("request-progress", {
431
+ requestId,
432
+ timestamp: Date.now(),
433
+ loaded: event.loaded,
434
+ total: event.total,
435
+ lengthComputable: event.lengthComputable
436
+ });
437
+ });
264
438
  request.addEventListener("readystatechange", () => {
265
439
  if (request.readyState === READY_STATE_HEADERS_RECEIVED) {
266
440
  ttfb = Date.now() - sendTime;
@@ -275,7 +449,9 @@ const getNetworkInspector = (pluginClient) => {
275
449
  url: request._url,
276
450
  status: request.status,
277
451
  statusText: request.statusText,
278
- headers: request.responseHeaders || {},
452
+ headers: applyReactNativeResponseHeadersLogic(
453
+ request.responseHeaders || {}
454
+ ),
279
455
  contentType: getContentType(request),
280
456
  size: getResponseSize(request),
281
457
  responseTime: Date.now()
@@ -309,10 +485,55 @@ const getNetworkInspector = (pluginClient) => {
309
485
  canceled: true
310
486
  });
311
487
  });
488
+ request.addEventListener("timeout", () => {
489
+ pluginClient.send("request-failed", {
490
+ requestId,
491
+ timestamp: Date.now(),
492
+ type: "XHR",
493
+ error: "Timeout",
494
+ canceled: false
495
+ });
496
+ });
497
+ };
498
+ const handleRequestOverride = (request) => {
499
+ const override = overridesRegistry$1.getOverrideForUrl(
500
+ request._url
501
+ );
502
+ if (!override) {
503
+ return;
504
+ }
505
+ request.addEventListener("readystatechange", () => {
506
+ if (override.body !== void 0) {
507
+ Object.defineProperty(request, "responseType", {
508
+ writable: true
509
+ });
510
+ Object.defineProperty(request, "response", {
511
+ writable: true
512
+ });
513
+ Object.defineProperty(request, "responseText", {
514
+ writable: true
515
+ });
516
+ const contentType = getContentType(request);
517
+ if (contentType === "application/json") {
518
+ request.responseType = "json";
519
+ } else if (contentType === "text/plain") {
520
+ request.responseType = "text";
521
+ }
522
+ request.response = override.body;
523
+ request.responseText = override.body;
524
+ }
525
+ if (override.status !== void 0) {
526
+ Object.defineProperty(request, "status", {
527
+ writable: true
528
+ });
529
+ request.status = override.status;
530
+ }
531
+ });
312
532
  };
313
533
  const enable = () => {
314
534
  XHRInterceptor.disableInterception();
315
535
  XHRInterceptor.setSendCallback(handleRequestSend);
536
+ XHRInterceptor.setOverrideCallback(handleRequestOverride);
316
537
  XHRInterceptor.enableInterception();
317
538
  };
318
539
  const disable = () => {
@@ -518,6 +739,8 @@ let closeCallback;
518
739
  let isInterceptorEnabled = false;
519
740
  const eventSourceClass = getEventSource();
520
741
  const originalOpen = eventSourceClass.prototype.open;
742
+ const originalDispatch = eventSourceClass.prototype.dispatch;
743
+ const BUILT_IN_EVENT_TYPES = /* @__PURE__ */ new Set(["open", "error", "close", "done"]);
521
744
  const SSEInterceptor = {
522
745
  /**
523
746
  * Invoked when EventSource.open() is called (connection attempt starting).
@@ -565,11 +788,6 @@ const SSEInterceptor = {
565
788
  openEventCallback(event, this);
566
789
  }
567
790
  });
568
- this.addEventListener("message", (event) => {
569
- if (messageCallback) {
570
- messageCallback(event, this);
571
- }
572
- });
573
791
  this.addEventListener(
574
792
  "error",
575
793
  (event) => {
@@ -585,6 +803,14 @@ const SSEInterceptor = {
585
803
  });
586
804
  return originalOpen.call(this);
587
805
  };
806
+ eventSourceClass.prototype.dispatch = function(eventType, data) {
807
+ if (!BUILT_IN_EVENT_TYPES.has(eventType)) {
808
+ if (messageCallback) {
809
+ messageCallback(data, this);
810
+ }
811
+ }
812
+ return originalDispatch.call(this, eventType, data);
813
+ };
588
814
  isInterceptorEnabled = true;
589
815
  },
590
816
  // Unpatch EventSource open method and remove the callbacks.
@@ -594,6 +820,7 @@ const SSEInterceptor = {
594
820
  }
595
821
  isInterceptorEnabled = false;
596
822
  eventSourceClass.prototype.open = originalOpen;
823
+ eventSourceClass.prototype.dispatch = originalDispatch;
597
824
  connectCallback = null;
598
825
  messageCallback = null;
599
826
  errorCallback = null;
@@ -607,9 +834,7 @@ const getSSEInspector = () => {
607
834
  var _a;
608
835
  const requestId = (_a = eventSource._xhr) == null ? void 0 : _a._rozeniteRequestId;
609
836
  if (!requestId) {
610
- throw new Error(
611
- "No request ID found for EventSource. This should never happen!"
612
- );
837
+ return null;
613
838
  }
614
839
  return requestId;
615
840
  };
@@ -618,6 +843,9 @@ const getSSEInspector = () => {
618
843
  SSEInterceptor.setOpenEventCallback((_, eventSource) => {
619
844
  const sseEventSource = eventSource;
620
845
  const requestId = getRequestId(sseEventSource);
846
+ if (!requestId) {
847
+ return;
848
+ }
621
849
  const sseXhr = sseEventSource._xhr;
622
850
  const event = {
623
851
  type: "sse-open",
@@ -638,17 +866,26 @@ const getSSEInspector = () => {
638
866
  SSEInterceptor.setMessageCallback((messageEvent, eventSource) => {
639
867
  const sseEventSource = eventSource;
640
868
  const requestId = getRequestId(sseEventSource);
869
+ if (!requestId) {
870
+ return;
871
+ }
641
872
  const event = {
642
873
  type: "sse-message",
643
874
  requestId,
644
875
  timestamp: Date.now(),
645
- data: messageEvent.data || ""
876
+ payload: {
877
+ type: messageEvent.type,
878
+ data: messageEvent.data || ""
879
+ }
646
880
  };
647
881
  eventEmitter.emit("sse-message", event);
648
882
  });
649
883
  SSEInterceptor.setErrorCallback((errorEvent, eventSource) => {
650
884
  const sseEventSource = eventSource;
651
885
  const requestId = getRequestId(sseEventSource);
886
+ if (!requestId) {
887
+ return;
888
+ }
652
889
  const event = {
653
890
  type: "sse-error",
654
891
  requestId,
@@ -663,6 +900,9 @@ const getSSEInspector = () => {
663
900
  SSEInterceptor.setCloseCallback((_, eventSource) => {
664
901
  const sseEventSource = eventSource;
665
902
  const requestId = getRequestId(sseEventSource);
903
+ if (!requestId) {
904
+ return;
905
+ }
666
906
  const event = {
667
907
  type: "sse-close",
668
908
  requestId,
@@ -677,26 +917,93 @@ const getSSEInspector = () => {
677
917
  },
678
918
  isEnabled: () => SSEInterceptor.isInterceptorEnabled(),
679
919
  dispose: () => {
920
+ SSEInterceptor.disableInterception();
680
921
  eventEmitter.events = {};
681
922
  },
682
923
  on: (event, callback) => eventEmitter.on(event, callback)
683
924
  };
684
925
  };
685
- const useNetworkActivityDevTools = () => {
926
+ const DEFAULT_CONFIG = {
927
+ inspectors: {
928
+ http: true,
929
+ websocket: true,
930
+ sse: true
931
+ },
932
+ clientUISettings: {
933
+ showUrlAsName: false
934
+ }
935
+ };
936
+ const validateConfig = (config) => {
937
+ const inspectors = config.inspectors;
938
+ if (!inspectors) {
939
+ return;
940
+ }
941
+ if (inspectors.sse && !inspectors.http) {
942
+ throw new Error("SSE inspector requires HTTP inspector to be enabled.");
943
+ }
944
+ };
945
+ const overridesRegistry = getOverridesRegistry();
946
+ const useNetworkActivityDevTools = (config = DEFAULT_CONFIG) => {
947
+ var _a, _b, _c, _d;
948
+ const isRecordingEnabledRef = useRef(false);
686
949
  const client = useRozeniteDevToolsClient({
687
950
  pluginId: "@rozenite/network-activity-plugin"
688
951
  });
952
+ const isHttpInspectorEnabled = ((_a = config.inspectors) == null ? void 0 : _a.http) ?? true;
953
+ const isWebSocketInspectorEnabled = ((_b = config.inspectors) == null ? void 0 : _b.websocket) ?? true;
954
+ const isSSEInspectorEnabled = ((_c = config.inspectors) == null ? void 0 : _c.sse) ?? true;
955
+ const showUrlAsName = (_d = config.clientUISettings) == null ? void 0 : _d.showUrlAsName;
689
956
  useEffect(() => {
690
957
  if (!client) {
691
958
  return;
692
959
  }
960
+ validateConfig(config);
961
+ }, [config]);
962
+ useEffect(() => {
963
+ if (!client) {
964
+ return;
965
+ }
966
+ const sendClientUISettings = () => {
967
+ var _a2;
968
+ client.send("client-ui-settings", {
969
+ settings: {
970
+ showUrlAsName: showUrlAsName ?? ((_a2 = DEFAULT_CONFIG.clientUISettings) == null ? void 0 : _a2.showUrlAsName)
971
+ }
972
+ });
973
+ };
974
+ const subscriptions = [
975
+ client.onMessage("network-enable", () => {
976
+ isRecordingEnabledRef.current = true;
977
+ }),
978
+ client.onMessage("network-disable", () => {
979
+ isRecordingEnabledRef.current = false;
980
+ }),
981
+ client.onMessage("set-overrides", (data) => {
982
+ overridesRegistry.setOverrides(data.overrides);
983
+ }),
984
+ client.onMessage("get-client-ui-settings", () => {
985
+ sendClientUISettings();
986
+ })
987
+ ];
988
+ sendClientUISettings();
989
+ return () => {
990
+ subscriptions.forEach((subscription) => subscription.remove());
991
+ };
992
+ }, [client, showUrlAsName]);
993
+ useEffect(() => {
994
+ if (!client || !isHttpInspectorEnabled) {
995
+ return;
996
+ }
693
997
  const networkInspector = getNetworkInspector(client);
998
+ if (isRecordingEnabledRef.current) {
999
+ networkInspector.enable();
1000
+ }
694
1001
  return () => {
695
1002
  networkInspector.dispose();
696
1003
  };
697
- }, [client]);
1004
+ }, [client, isHttpInspectorEnabled]);
698
1005
  useEffect(() => {
699
- if (!client) {
1006
+ if (!client || !isWebSocketInspectorEnabled) {
700
1007
  return;
701
1008
  }
702
1009
  const eventsToForward = [
@@ -720,12 +1027,15 @@ const useNetworkActivityDevTools = () => {
720
1027
  client.onMessage("network-disable", () => {
721
1028
  websocketInspector.disable();
722
1029
  });
1030
+ if (isRecordingEnabledRef.current) {
1031
+ websocketInspector.enable();
1032
+ }
723
1033
  return () => {
724
1034
  websocketInspector.dispose();
725
1035
  };
726
- }, [client]);
1036
+ }, [client, isWebSocketInspectorEnabled]);
727
1037
  useEffect(() => {
728
- if (!client) {
1038
+ if (!client || !isSSEInspectorEnabled) {
729
1039
  return;
730
1040
  }
731
1041
  const eventsToForward = [
@@ -746,10 +1056,13 @@ const useNetworkActivityDevTools = () => {
746
1056
  client.onMessage("network-disable", () => {
747
1057
  sseInspector.disable();
748
1058
  });
1059
+ if (isRecordingEnabledRef.current) {
1060
+ sseInspector.enable();
1061
+ }
749
1062
  return () => {
750
1063
  sseInspector.dispose();
751
1064
  };
752
- }, [client]);
1065
+ }, [client, isSSEInspectorEnabled]);
753
1066
  return client;
754
1067
  };
755
1068
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/network-activity-plugin",
3
- "version": "1.0.0-alpha.9",
3
+ "version": "1.1.0",
4
4
  "description": "Network Activity for Rozenite.",
5
5
  "type": "module",
6
6
  "main": "./dist/react-native.cjs",
@@ -8,7 +8,7 @@
8
8
  "types": "./dist/react-native.d.ts",
9
9
  "dependencies": {
10
10
  "nanoevents": "^9.1.0",
11
- "@rozenite/plugin-bridge": "1.0.0-alpha.9"
11
+ "@rozenite/plugin-bridge": "1.1.0"
12
12
  },
13
13
  "devDependencies": {
14
14
  "@floating-ui/react": "^0.26.0",
@@ -16,6 +16,7 @@
16
16
  "@radix-ui/react-separator": "^1.1.7",
17
17
  "@radix-ui/react-slot": "^1.2.3",
18
18
  "@radix-ui/react-tabs": "^1.1.12",
19
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
19
20
  "@tanstack/react-table": "^8.21.3",
20
21
  "@tanstack/react-virtual": "^3.0.0",
21
22
  "autoprefixer": "^10.4.21",
@@ -33,8 +34,10 @@
33
34
  "typescript": "^5.7.3",
34
35
  "vite": "^6.0.0",
35
36
  "zustand": "^5.0.6",
36
- "@rozenite/vite-plugin": "1.0.0-alpha.9",
37
- "rozenite": "1.0.0-alpha.9"
37
+ "@types/react": "~18.3.23",
38
+ "@types/react-dom": "~18.3.1",
39
+ "@rozenite/vite-plugin": "1.1.0",
40
+ "rozenite": "1.1.0"
38
41
  },
39
42
  "peerDependencies": {
40
43
  "react-native-sse": "*"
package/react-native.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  export let useNetworkActivityDevTools: typeof import('./src/react-native/useNetworkActivityDevTools').useNetworkActivityDevTools;
2
2
 
3
- if (process.env.NODE_ENV !== 'production') {
3
+ const isWeb =
4
+ typeof window !== 'undefined' && window.navigator.product !== 'ReactNative';
5
+ const isDev = process.env.NODE_ENV !== 'production';
6
+ const isServer = typeof window === 'undefined';
7
+
8
+ if (isDev && !isWeb && !isServer) {
4
9
  useNetworkActivityDevTools =
5
10
  require('./src/react-native/useNetworkActivityDevTools').useNetworkActivityDevTools;
6
11
  } else {
@@ -0,0 +1,43 @@
1
+ export type InspectorType = 'http' | 'websocket' | 'sse';
2
+
3
+ export type NetworkActivityDevToolsConfig = {
4
+ /**
5
+ * Specifies which network inspectors are enabled.
6
+ * Set to `false` to disable monitoring for a specific type of network traffic.
7
+ * @default { http: true, websocket: true, sse: true }
8
+ */
9
+ inspectors?: {
10
+ [key in InspectorType]?: boolean;
11
+ };
12
+ clientUISettings?: {
13
+ /**
14
+ * If true, display the entire relative URL as the request name in the UI instead of only the last path segment.
15
+ * @default false
16
+ */
17
+ showUrlAsName?: boolean;
18
+ };
19
+ };
20
+
21
+ export const DEFAULT_CONFIG: NetworkActivityDevToolsConfig = {
22
+ inspectors: {
23
+ http: true,
24
+ websocket: true,
25
+ sse: true,
26
+ },
27
+ clientUISettings: {
28
+ showUrlAsName: false,
29
+ }
30
+ };
31
+
32
+ export const validateConfig = (config: NetworkActivityDevToolsConfig): void => {
33
+ const inspectors = config.inspectors;
34
+
35
+ if (!inspectors) {
36
+ return;
37
+ }
38
+
39
+ // For SSE, HTTP must be enabled
40
+ if (inspectors.sse && !inspectors.http) {
41
+ throw new Error('SSE inspector requires HTTP inspector to be enabled.');
42
+ }
43
+ };