@rozenite/network-activity-plugin 1.0.0-alpha.9 → 1.0.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 (111) 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-C6wCDVkW.js} +8157 -2677
  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 +48 -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 +12 -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 +319 -24
  52. package/dist/useNetworkActivityDevTools.js +320 -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 +170 -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 +73 -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 +65 -13
  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/globals.css +4 -0
  85. package/src/ui/hooks/useCopyToClipboard.ts +2 -2
  86. package/src/ui/state/hooks.ts +8 -0
  87. package/src/ui/state/model.ts +18 -7
  88. package/src/ui/state/store.ts +610 -500
  89. package/src/ui/tabs/CookiesTab.tsx +60 -263
  90. package/src/ui/tabs/HeadersTab.tsx +78 -89
  91. package/src/ui/tabs/RequestTab.tsx +58 -46
  92. package/src/ui/tabs/ResponseTab.tsx +98 -67
  93. package/src/ui/tabs/SSEMessagesTab.tsx +50 -39
  94. package/src/ui/utils/checkRequestBodyBinary.ts +7 -0
  95. package/src/ui/utils/escapeShellArg.ts +12 -0
  96. package/src/ui/utils/generateCurlCommand.ts +83 -0
  97. package/src/ui/utils/generateFetchCall.ts +64 -0
  98. package/src/ui/utils/generateMultipartBody.ts +19 -0
  99. package/src/ui/views/InspectorView.tsx +15 -3
  100. package/src/utils/applyReactNativeRequestHeadersLogic.ts +30 -0
  101. package/src/utils/applyReactNativeResponseHeadersLogic.ts +28 -0
  102. package/src/utils/cookieParser.ts +126 -0
  103. package/src/utils/getContentTypeMimeType.ts +17 -0
  104. package/src/utils/getHttpHeader.ts +17 -0
  105. package/src/utils/getHttpHeaderValueAsString.ts +13 -0
  106. package/src/utils/getStringSizeInBytes.ts +3 -0
  107. package/src/utils/inferContentTypeFromPostData.ts +9 -0
  108. package/src/utils/safeStringify.ts +7 -0
  109. package/src/utils/typeChecks.ts +27 -0
  110. package/dist/src/ui/utils/getHttpHeaderValue.d.ts +0 -2
  111. 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,7 +421,7 @@ 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
@@ -275,7 +440,9 @@ const getNetworkInspector = (pluginClient) => {
275
440
  url: request._url,
276
441
  status: request.status,
277
442
  statusText: request.statusText,
278
- headers: request.responseHeaders || {},
443
+ headers: applyReactNativeResponseHeadersLogic(
444
+ request.responseHeaders || {}
445
+ ),
279
446
  contentType: getContentType(request),
280
447
  size: getResponseSize(request),
281
448
  responseTime: Date.now()
@@ -310,9 +477,45 @@ const getNetworkInspector = (pluginClient) => {
310
477
  });
311
478
  });
312
479
  };
480
+ const handleRequestOverride = (request) => {
481
+ const override = overridesRegistry$1.getOverrideForUrl(
482
+ request._url
483
+ );
484
+ if (!override) {
485
+ return;
486
+ }
487
+ request.addEventListener("readystatechange", () => {
488
+ if (override.body !== void 0) {
489
+ Object.defineProperty(request, "responseType", {
490
+ writable: true
491
+ });
492
+ Object.defineProperty(request, "response", {
493
+ writable: true
494
+ });
495
+ Object.defineProperty(request, "responseText", {
496
+ writable: true
497
+ });
498
+ const contentType = getContentType(request);
499
+ if (contentType === "application/json") {
500
+ request.responseType = "json";
501
+ } else if (contentType === "text/plain") {
502
+ request.responseType = "text";
503
+ }
504
+ request.response = override.body;
505
+ request.responseText = override.body;
506
+ }
507
+ if (override.status !== void 0) {
508
+ Object.defineProperty(request, "status", {
509
+ writable: true
510
+ });
511
+ request.status = override.status;
512
+ }
513
+ });
514
+ };
313
515
  const enable = () => {
314
516
  XHRInterceptor.disableInterception();
315
517
  XHRInterceptor.setSendCallback(handleRequestSend);
518
+ XHRInterceptor.setOverrideCallback(handleRequestOverride);
316
519
  XHRInterceptor.enableInterception();
317
520
  };
318
521
  const disable = () => {
@@ -518,6 +721,8 @@ let closeCallback;
518
721
  let isInterceptorEnabled = false;
519
722
  const eventSourceClass = getEventSource();
520
723
  const originalOpen = eventSourceClass.prototype.open;
724
+ const originalDispatch = eventSourceClass.prototype.dispatch;
725
+ const BUILT_IN_EVENT_TYPES = /* @__PURE__ */ new Set(["open", "error", "close", "done"]);
521
726
  const SSEInterceptor = {
522
727
  /**
523
728
  * Invoked when EventSource.open() is called (connection attempt starting).
@@ -565,11 +770,6 @@ const SSEInterceptor = {
565
770
  openEventCallback(event, this);
566
771
  }
567
772
  });
568
- this.addEventListener("message", (event) => {
569
- if (messageCallback) {
570
- messageCallback(event, this);
571
- }
572
- });
573
773
  this.addEventListener(
574
774
  "error",
575
775
  (event) => {
@@ -585,6 +785,14 @@ const SSEInterceptor = {
585
785
  });
586
786
  return originalOpen.call(this);
587
787
  };
788
+ eventSourceClass.prototype.dispatch = function(eventType, data) {
789
+ if (!BUILT_IN_EVENT_TYPES.has(eventType)) {
790
+ if (messageCallback) {
791
+ messageCallback(data, this);
792
+ }
793
+ }
794
+ return originalDispatch.call(this, eventType, data);
795
+ };
588
796
  isInterceptorEnabled = true;
589
797
  },
590
798
  // Unpatch EventSource open method and remove the callbacks.
@@ -594,6 +802,7 @@ const SSEInterceptor = {
594
802
  }
595
803
  isInterceptorEnabled = false;
596
804
  eventSourceClass.prototype.open = originalOpen;
805
+ eventSourceClass.prototype.dispatch = originalDispatch;
597
806
  connectCallback = null;
598
807
  messageCallback = null;
599
808
  errorCallback = null;
@@ -607,9 +816,7 @@ const getSSEInspector = () => {
607
816
  var _a;
608
817
  const requestId = (_a = eventSource._xhr) == null ? void 0 : _a._rozeniteRequestId;
609
818
  if (!requestId) {
610
- throw new Error(
611
- "No request ID found for EventSource. This should never happen!"
612
- );
819
+ return null;
613
820
  }
614
821
  return requestId;
615
822
  };
@@ -618,6 +825,9 @@ const getSSEInspector = () => {
618
825
  SSEInterceptor.setOpenEventCallback((_, eventSource) => {
619
826
  const sseEventSource = eventSource;
620
827
  const requestId = getRequestId(sseEventSource);
828
+ if (!requestId) {
829
+ return;
830
+ }
621
831
  const sseXhr = sseEventSource._xhr;
622
832
  const event = {
623
833
  type: "sse-open",
@@ -638,17 +848,26 @@ const getSSEInspector = () => {
638
848
  SSEInterceptor.setMessageCallback((messageEvent, eventSource) => {
639
849
  const sseEventSource = eventSource;
640
850
  const requestId = getRequestId(sseEventSource);
851
+ if (!requestId) {
852
+ return;
853
+ }
641
854
  const event = {
642
855
  type: "sse-message",
643
856
  requestId,
644
857
  timestamp: Date.now(),
645
- data: messageEvent.data || ""
858
+ payload: {
859
+ type: messageEvent.type,
860
+ data: messageEvent.data || ""
861
+ }
646
862
  };
647
863
  eventEmitter.emit("sse-message", event);
648
864
  });
649
865
  SSEInterceptor.setErrorCallback((errorEvent, eventSource) => {
650
866
  const sseEventSource = eventSource;
651
867
  const requestId = getRequestId(sseEventSource);
868
+ if (!requestId) {
869
+ return;
870
+ }
652
871
  const event = {
653
872
  type: "sse-error",
654
873
  requestId,
@@ -663,6 +882,9 @@ const getSSEInspector = () => {
663
882
  SSEInterceptor.setCloseCallback((_, eventSource) => {
664
883
  const sseEventSource = eventSource;
665
884
  const requestId = getRequestId(sseEventSource);
885
+ if (!requestId) {
886
+ return;
887
+ }
666
888
  const event = {
667
889
  type: "sse-close",
668
890
  requestId,
@@ -677,26 +899,93 @@ const getSSEInspector = () => {
677
899
  },
678
900
  isEnabled: () => SSEInterceptor.isInterceptorEnabled(),
679
901
  dispose: () => {
902
+ SSEInterceptor.disableInterception();
680
903
  eventEmitter.events = {};
681
904
  },
682
905
  on: (event, callback) => eventEmitter.on(event, callback)
683
906
  };
684
907
  };
685
- const useNetworkActivityDevTools = () => {
908
+ const DEFAULT_CONFIG = {
909
+ inspectors: {
910
+ http: true,
911
+ websocket: true,
912
+ sse: true
913
+ },
914
+ clientUISettings: {
915
+ showUrlAsName: false
916
+ }
917
+ };
918
+ const validateConfig = (config) => {
919
+ const inspectors = config.inspectors;
920
+ if (!inspectors) {
921
+ return;
922
+ }
923
+ if (inspectors.sse && !inspectors.http) {
924
+ throw new Error("SSE inspector requires HTTP inspector to be enabled.");
925
+ }
926
+ };
927
+ const overridesRegistry = getOverridesRegistry();
928
+ const useNetworkActivityDevTools = (config = DEFAULT_CONFIG) => {
929
+ var _a, _b, _c, _d;
930
+ const isRecordingEnabledRef = useRef(false);
686
931
  const client = useRozeniteDevToolsClient({
687
932
  pluginId: "@rozenite/network-activity-plugin"
688
933
  });
934
+ const isHttpInspectorEnabled = ((_a = config.inspectors) == null ? void 0 : _a.http) ?? true;
935
+ const isWebSocketInspectorEnabled = ((_b = config.inspectors) == null ? void 0 : _b.websocket) ?? true;
936
+ const isSSEInspectorEnabled = ((_c = config.inspectors) == null ? void 0 : _c.sse) ?? true;
937
+ const showUrlAsName = (_d = config.clientUISettings) == null ? void 0 : _d.showUrlAsName;
689
938
  useEffect(() => {
690
939
  if (!client) {
691
940
  return;
692
941
  }
942
+ validateConfig(config);
943
+ }, [config]);
944
+ useEffect(() => {
945
+ if (!client) {
946
+ return;
947
+ }
948
+ const sendClientUISettings = () => {
949
+ var _a2;
950
+ client.send("client-ui-settings", {
951
+ settings: {
952
+ showUrlAsName: showUrlAsName ?? ((_a2 = DEFAULT_CONFIG.clientUISettings) == null ? void 0 : _a2.showUrlAsName)
953
+ }
954
+ });
955
+ };
956
+ const subscriptions = [
957
+ client.onMessage("network-enable", () => {
958
+ isRecordingEnabledRef.current = true;
959
+ }),
960
+ client.onMessage("network-disable", () => {
961
+ isRecordingEnabledRef.current = false;
962
+ }),
963
+ client.onMessage("set-overrides", (data) => {
964
+ overridesRegistry.setOverrides(data.overrides);
965
+ }),
966
+ client.onMessage("get-client-ui-settings", () => {
967
+ sendClientUISettings();
968
+ })
969
+ ];
970
+ sendClientUISettings();
971
+ return () => {
972
+ subscriptions.forEach((subscription) => subscription.remove());
973
+ };
974
+ }, [client, showUrlAsName]);
975
+ useEffect(() => {
976
+ if (!client || !isHttpInspectorEnabled) {
977
+ return;
978
+ }
693
979
  const networkInspector = getNetworkInspector(client);
980
+ if (isRecordingEnabledRef.current) {
981
+ networkInspector.enable();
982
+ }
694
983
  return () => {
695
984
  networkInspector.dispose();
696
985
  };
697
- }, [client]);
986
+ }, [client, isHttpInspectorEnabled]);
698
987
  useEffect(() => {
699
- if (!client) {
988
+ if (!client || !isWebSocketInspectorEnabled) {
700
989
  return;
701
990
  }
702
991
  const eventsToForward = [
@@ -720,12 +1009,15 @@ const useNetworkActivityDevTools = () => {
720
1009
  client.onMessage("network-disable", () => {
721
1010
  websocketInspector.disable();
722
1011
  });
1012
+ if (isRecordingEnabledRef.current) {
1013
+ websocketInspector.enable();
1014
+ }
723
1015
  return () => {
724
1016
  websocketInspector.dispose();
725
1017
  };
726
- }, [client]);
1018
+ }, [client, isWebSocketInspectorEnabled]);
727
1019
  useEffect(() => {
728
- if (!client) {
1020
+ if (!client || !isSSEInspectorEnabled) {
729
1021
  return;
730
1022
  }
731
1023
  const eventsToForward = [
@@ -746,10 +1038,13 @@ const useNetworkActivityDevTools = () => {
746
1038
  client.onMessage("network-disable", () => {
747
1039
  sseInspector.disable();
748
1040
  });
1041
+ if (isRecordingEnabledRef.current) {
1042
+ sseInspector.enable();
1043
+ }
749
1044
  return () => {
750
1045
  sseInspector.dispose();
751
1046
  };
752
- }, [client]);
1047
+ }, [client, isSSEInspectorEnabled]);
753
1048
  return client;
754
1049
  };
755
1050
  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.0.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.0.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.0.0",
40
+ "rozenite": "1.0.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
+ };