@rozenite/network-activity-plugin 1.0.0-alpha.11 → 1.0.0-alpha.12

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 (48) hide show
  1. package/README.md +2 -0
  2. package/dist/App.html +2 -2
  3. package/dist/assets/{App-Ct73Yrm6.css → App-DCuHdq4D.css} +17 -0
  4. package/dist/assets/{App-BKBLGSeM.js → App-JuOeT_VQ.js} +2693 -2642
  5. package/dist/rozenite.json +1 -1
  6. package/dist/src/react-native/config.d.ts +13 -0
  7. package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -1
  8. package/dist/src/shared/client.d.ts +15 -3
  9. package/dist/src/ui/components/Button.d.ts +1 -1
  10. package/dist/src/ui/components/CodeBlock.d.ts +3 -0
  11. package/dist/src/ui/components/CookieCard.d.ts +7 -0
  12. package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +1 -1
  13. package/dist/src/ui/components/Section.d.ts +2 -1
  14. package/dist/src/ui/state/model.d.ts +4 -4
  15. package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +10 -0
  16. package/dist/src/utils/cookieParser.d.ts +6 -0
  17. package/dist/src/utils/getHttpHeader.d.ts +5 -0
  18. package/dist/src/utils/getStringSizeInBytes.d.ts +1 -0
  19. package/dist/src/utils/isNumber.d.ts +1 -0
  20. package/dist/useNetworkActivityDevTools.cjs +115 -19
  21. package/dist/useNetworkActivityDevTools.js +116 -20
  22. package/package.json +4 -4
  23. package/src/react-native/config.ts +33 -0
  24. package/src/react-native/http/network-inspector.ts +36 -10
  25. package/src/react-native/sse/sse-inspector.ts +1 -0
  26. package/src/react-native/useNetworkActivityDevTools.ts +63 -8
  27. package/src/shared/client.ts +17 -3
  28. package/src/ui/components/CodeBlock.tsx +19 -0
  29. package/src/ui/components/CookieCard.tsx +64 -0
  30. package/src/ui/components/JsonTree.tsx +10 -3
  31. package/src/ui/components/JsonTreeCopyableItem.tsx +14 -10
  32. package/src/ui/components/RequestList.tsx +15 -5
  33. package/src/ui/components/Section.tsx +31 -4
  34. package/src/ui/state/model.ts +4 -4
  35. package/src/ui/tabs/CookiesTab.tsx +64 -263
  36. package/src/ui/tabs/HeadersTab.tsx +26 -20
  37. package/src/ui/tabs/RequestTab.tsx +62 -47
  38. package/src/ui/tabs/ResponseTab.tsx +54 -69
  39. package/src/utils/applyReactNativeRequestHeadersLogic.ts +2 -2
  40. package/src/utils/applyReactNativeResponseHeadersLogic.ts +29 -0
  41. package/src/utils/cookieParser.ts +126 -0
  42. package/src/utils/getContentTypeMimeType.ts +10 -5
  43. package/src/utils/getHttpHeader.ts +17 -0
  44. package/src/utils/getStringSizeInBytes.ts +3 -0
  45. package/src/utils/isNumber.ts +3 -0
  46. package/src/utils/safeStringify.ts +1 -1
  47. package/dist/src/utils/getHttpHeaderValue.d.ts +0 -2
  48. package/src/utils/getHttpHeaderValue.ts +0 -14
@@ -1 +1 @@
1
- {"name":"@rozenite/network-activity-plugin","version":"1.0.0-alpha.10","description":"Network Activity for Rozenite.","panels":[{"name":"Network Activity","source":"/App.html"}]}
1
+ {"name":"@rozenite/network-activity-plugin","version":"1.0.0-alpha.11","description":"Network Activity for Rozenite.","panels":[{"name":"Network Activity","source":"/App.html"}]}
@@ -0,0 +1,13 @@
1
+ export type InspectorType = 'http' | 'websocket' | 'sse';
2
+ export type NetworkActivityDevToolsConfig = {
3
+ /**
4
+ * Specifies which network inspectors are enabled.
5
+ * Set to `false` to disable monitoring for a specific type of network traffic.
6
+ * @default { http: true, websocket: true, sse: true }
7
+ */
8
+ inspectors?: {
9
+ [key in InspectorType]?: boolean;
10
+ };
11
+ };
12
+ export declare const DEFAULT_CONFIG: NetworkActivityDevToolsConfig;
13
+ export declare const validateConfig: (config: NetworkActivityDevToolsConfig) => void;
@@ -1,2 +1,3 @@
1
1
  import { NetworkActivityEventMap } from '../shared/client';
2
- export declare const useNetworkActivityDevTools: () => import('@rozenite/plugin-bridge').RozeniteDevToolsClient<NetworkActivityEventMap> | null;
2
+ import { NetworkActivityDevToolsConfig } from './config';
3
+ export declare const useNetworkActivityDevTools: (config?: NetworkActivityDevToolsConfig) => import('@rozenite/plugin-bridge').RozeniteDevToolsClient<NetworkActivityEventMap> | null;
@@ -1,7 +1,8 @@
1
1
  import { RozeniteDevToolsClient } from '@rozenite/plugin-bridge';
2
2
  import { WebSocketEventMap } from './websocket-events';
3
3
  import { SSEEventMap } from './sse-events';
4
- export type HttpHeaders = Record<string, string>;
4
+ export type HttpHeaders = Record<string, string | string[]>;
5
+ export type XHRHeaders = NonNullable<XMLHttpRequest['responseHeaders']>;
5
6
  export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
6
7
  export type RequestId = string;
7
8
  export type Timestamp = number;
@@ -20,6 +21,17 @@ export type RequestPostData = {
20
21
  name?: string;
21
22
  };
22
23
  } | null | undefined;
24
+ export type Cookie = {
25
+ name: string;
26
+ value: string;
27
+ domain?: string;
28
+ path?: string;
29
+ expires?: string;
30
+ maxAge?: string;
31
+ secure?: boolean;
32
+ httpOnly?: boolean;
33
+ sameSite?: string;
34
+ };
23
35
  export type Request = {
24
36
  url: string;
25
37
  method: HttpMethod;
@@ -32,7 +44,7 @@ export type Response = {
32
44
  statusText: string;
33
45
  headers: HttpHeaders;
34
46
  contentType: string;
35
- size: number;
47
+ size: number | null;
36
48
  responseTime: Timestamp;
37
49
  };
38
50
  export type Initiator = {
@@ -62,7 +74,7 @@ export type NetworkActivityEventMap = {
62
74
  requestId: RequestId;
63
75
  timestamp: Timestamp;
64
76
  duration: number;
65
- size: number;
77
+ size: number | null;
66
78
  ttfb: number;
67
79
  };
68
80
  'request-failed': {
@@ -1,7 +1,7 @@
1
1
  import { VariantProps } from 'class-variance-authority';
2
2
  import * as React from 'react';
3
3
  declare const buttonVariants: (props?: ({
4
- variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
4
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
5
5
  size?: "default" | "xs" | "sm" | "lg" | "icon" | null | undefined;
6
6
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -0,0 +1,3 @@
1
+ import { HTMLProps } from 'react';
2
+ export type CodeBlockProps = HTMLProps<HTMLPreElement>;
3
+ export declare const CodeBlock: ({ children, className, ...props }: CodeBlockProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { Cookie } from '../../shared/client';
2
+ type CookieCardProps = {
3
+ cookie: Cookie;
4
+ keyClassName?: string;
5
+ };
6
+ export declare const CookieCard: ({ cookie, keyClassName }: CookieCardProps) => import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -3,5 +3,5 @@ type JsonTreeCopyableItemProps = PropsWithChildren<{
3
3
  getCopyableValue: () => string;
4
4
  className?: string;
5
5
  }>;
6
- export declare const JsonTreeCopyableItem: ({ children, getCopyableValue, className }: JsonTreeCopyableItemProps) => import("react/jsx-runtime").JSX.Element;
6
+ export declare const JsonTreeCopyableItem: ({ children, getCopyableValue, className, }: JsonTreeCopyableItemProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export {};
@@ -2,5 +2,6 @@ import { default as React } from 'react';
2
2
  export type SectionProps = {
3
3
  title: string;
4
4
  children: React.ReactNode;
5
+ collapsible?: boolean;
5
6
  };
6
- export declare const Section: ({ title, children }: SectionProps) => import("react/jsx-runtime").JSX.Element;
7
+ export declare const Section: ({ title, children, collapsible, }: SectionProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { Initiator, ResourceType, RequestPostData } from '../../shared/client';
1
+ import { Initiator, ResourceType, HttpHeaders, RequestPostData } from '../../shared/client';
2
2
  export type RequestId = string;
3
3
  export type Timestamp = number;
4
4
  export type SocketId = number;
@@ -15,14 +15,14 @@ export type HttpResponseData = {
15
15
  export type HttpRequest = {
16
16
  url: string;
17
17
  method: HttpMethod;
18
- headers: Record<string, string>;
18
+ headers: HttpHeaders;
19
19
  body?: HttpRequestData;
20
20
  };
21
21
  export type HttpResponse = {
22
22
  url: string;
23
23
  status: number;
24
24
  statusText: string;
25
- headers: Record<string, string>;
25
+ headers: HttpHeaders;
26
26
  contentType: string;
27
27
  size: number;
28
28
  responseTime: Timestamp;
@@ -97,7 +97,7 @@ export type ProcessedRequest = {
97
97
  status: HttpStatus | WebSocketStatus | SSEStatus;
98
98
  timestamp: Timestamp;
99
99
  duration?: number;
100
- size?: number;
100
+ size: number | null;
101
101
  method: HttpMethod | 'WS' | 'SSE';
102
102
  httpStatus?: number;
103
103
  };
@@ -0,0 +1,10 @@
1
+ import { HttpHeaders, XHRHeaders } from '../shared/client';
2
+ /**
3
+ * Applies React Native specific logic to response headers.
4
+ * React Native concatenates multiple header values into single strings,
5
+ * this function parses them back into arrays where appropriate.
6
+ *
7
+ * See:
8
+ * https://github.com/facebook/react-native/blob/588f0c5ce6c283f116228456da2170d2adc3cbf4/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java#L637
9
+ */
10
+ export declare const applyReactNativeResponseHeadersLogic: (headers: XHRHeaders) => HttpHeaders;
@@ -0,0 +1,6 @@
1
+ import { Cookie, HttpHeaders } from '../shared/client';
2
+ export declare const parseSetCookieHeader: (setCookieStr: string) => Cookie;
3
+ export declare const splitSetCookieHeaderByComma: (header: string) => string[];
4
+ export declare const parseCookieHeader: (cookieString: string) => Cookie[];
5
+ export declare const parseRequestCookiesFromHeaders: (headers: HttpHeaders) => Cookie[];
6
+ export declare const parseResponseCookiesFromHeaders: (headers: HttpHeaders) => Cookie[];
@@ -0,0 +1,5 @@
1
+ import { HttpHeaders, XHRHeaders } from '../shared/client';
2
+ export declare function getHttpHeader<T extends HttpHeaders | XHRHeaders>(headers: T, name: string): {
3
+ value: T[Extract<keyof T, string>];
4
+ originalKey: Extract<keyof T, string>;
5
+ } | undefined;
@@ -0,0 +1 @@
1
+ export declare const getStringSizeInBytes: (value: string) => number;
@@ -0,0 +1 @@
1
+ export declare const isNumber: (value: unknown) => value is number;
@@ -11,25 +11,27 @@ const WebSocketInterceptor__default = /* @__PURE__ */ _interopDefault(WebSocketI
11
11
  function safeStringify(data) {
12
12
  try {
13
13
  return typeof data === "string" ? data : JSON.stringify(data);
14
- } catch (error) {
14
+ } catch {
15
15
  return String(data);
16
16
  }
17
17
  }
18
- function getHttpHeaderValue(headers, name) {
18
+ function getHttpHeader(headers, name) {
19
19
  const lowerName = name.toLowerCase();
20
20
  for (const key in headers) {
21
21
  if (key.toLowerCase() === lowerName) {
22
- return headers[key];
22
+ return { value: headers[key], originalKey: key };
23
23
  }
24
24
  }
25
25
  return void 0;
26
26
  }
27
27
  function getContentTypeMime(headers) {
28
- const contentType = getHttpHeaderValue(headers, "content-type");
29
- if (contentType) {
30
- return contentType.split(";")[0].trim();
28
+ const contentType = getHttpHeader(headers, "content-type");
29
+ if (!contentType) {
30
+ return void 0;
31
31
  }
32
- return void 0;
32
+ const { value } = contentType;
33
+ const actualValue = Array.isArray(value) ? value[0] : value;
34
+ return actualValue.split(";")[0].trim();
33
35
  }
34
36
  const getContentType = (request) => {
35
37
  const responseHeaders = request.responseHeaders;
@@ -231,6 +233,28 @@ const XHRInterceptor = {
231
233
  requestHeaderCallback = null;
232
234
  }
233
235
  };
236
+ const getStringSizeInBytes = (value) => {
237
+ return new TextEncoder().encode(value).length;
238
+ };
239
+ const splitSetCookieHeaderByComma = (header) => {
240
+ const regex = /(?:^|,\s)([^=;,]+=[^;]+(?:;[^,]*)*)/g;
241
+ const matches = [];
242
+ let match;
243
+ while ((match = regex.exec(header)) !== null) {
244
+ matches.push(match[1].trim());
245
+ }
246
+ return matches;
247
+ };
248
+ const applyReactNativeResponseHeadersLogic = (headers) => {
249
+ const parsedHeaders = { ...headers };
250
+ const setCookieHeader = getHttpHeader(headers, "set-cookie");
251
+ if (setCookieHeader) {
252
+ const { value, originalKey } = setCookieHeader;
253
+ const cookies = splitSetCookieHeaderByComma(value);
254
+ parsedHeaders[originalKey] = cookies.length > 0 ? cookies : value;
255
+ }
256
+ return parsedHeaders;
257
+ };
234
258
  const networkRequestsRegistry = getNetworkRequestsRegistry();
235
259
  function getRequestBody(body) {
236
260
  if (body === null || body === void 0) {
@@ -266,13 +290,27 @@ function getRequestBody(body) {
266
290
  };
267
291
  }
268
292
  const getResponseSize = (request) => {
269
- if (request.response === null) {
293
+ try {
294
+ const { responseType, response } = request;
295
+ if (response === null) {
296
+ return 0;
297
+ }
298
+ if (responseType === "" || responseType === "text") {
299
+ return getStringSizeInBytes(request.responseText);
300
+ }
301
+ if (responseType === "json") {
302
+ return getStringSizeInBytes(safeStringify(response));
303
+ }
304
+ if (responseType === "blob") {
305
+ return response.size;
306
+ }
307
+ if (responseType === "arraybuffer") {
308
+ return response.byteLength;
309
+ }
270
310
  return 0;
311
+ } catch {
312
+ return null;
271
313
  }
272
- if (typeof request.response === "object") {
273
- return request.response.size;
274
- }
275
- return request.response.length || 0;
276
314
  };
277
315
  const getResponseBody = async (request) => {
278
316
  const responseType = request.responseType;
@@ -291,6 +329,9 @@ const getResponseBody = async (request) => {
291
329
  });
292
330
  }
293
331
  }
332
+ if (responseType === "json") {
333
+ return safeStringify(request.response);
334
+ }
294
335
  return null;
295
336
  };
296
337
  const getInitiatorFromStack = () => {
@@ -351,7 +392,9 @@ const getNetworkInspector = (pluginClient) => {
351
392
  url: request._url,
352
393
  status: request.status,
353
394
  statusText: request.statusText,
354
- headers: request.responseHeaders || {},
395
+ headers: applyReactNativeResponseHeadersLogic(
396
+ request.responseHeaders || {}
397
+ ),
355
398
  contentType: getContentType(request),
356
399
  size: getResponseSize(request),
357
400
  responseTime: Date.now()
@@ -762,26 +805,73 @@ const getSSEInspector = () => {
762
805
  },
763
806
  isEnabled: () => SSEInterceptor.isInterceptorEnabled(),
764
807
  dispose: () => {
808
+ SSEInterceptor.disableInterception();
765
809
  eventEmitter.events = {};
766
810
  },
767
811
  on: (event, callback) => eventEmitter.on(event, callback)
768
812
  };
769
813
  };
770
- const useNetworkActivityDevTools = () => {
814
+ const DEFAULT_CONFIG = {
815
+ inspectors: {
816
+ http: true,
817
+ websocket: true,
818
+ sse: true
819
+ }
820
+ };
821
+ const validateConfig = (config) => {
822
+ const inspectors = config.inspectors;
823
+ if (!inspectors) {
824
+ return;
825
+ }
826
+ if (inspectors.sse && !inspectors.http) {
827
+ throw new Error("SSE inspector requires HTTP inspector to be enabled.");
828
+ }
829
+ };
830
+ const useNetworkActivityDevTools = (config = DEFAULT_CONFIG) => {
831
+ var _a, _b, _c;
832
+ const isRecordingEnabledRef = react.useRef(false);
771
833
  const client = pluginBridge.useRozeniteDevToolsClient({
772
834
  pluginId: "@rozenite/network-activity-plugin"
773
835
  });
836
+ const isHttpInspectorEnabled = ((_a = config.inspectors) == null ? void 0 : _a.http) ?? true;
837
+ const isWebSocketInspectorEnabled = ((_b = config.inspectors) == null ? void 0 : _b.websocket) ?? true;
838
+ const isSSEInspectorEnabled = ((_c = config.inspectors) == null ? void 0 : _c.sse) ?? true;
839
+ react.useEffect(() => {
840
+ if (!client) {
841
+ return;
842
+ }
843
+ validateConfig(config);
844
+ }, [config]);
774
845
  react.useEffect(() => {
775
846
  if (!client) {
776
847
  return;
777
848
  }
849
+ const subscriptions = [
850
+ client.onMessage("network-enable", () => {
851
+ isRecordingEnabledRef.current = true;
852
+ }),
853
+ client.onMessage("network-disable", () => {
854
+ isRecordingEnabledRef.current = false;
855
+ })
856
+ ];
857
+ return () => {
858
+ subscriptions.forEach((subscription) => subscription.remove());
859
+ };
860
+ }, [client]);
861
+ react.useEffect(() => {
862
+ if (!client || !isHttpInspectorEnabled) {
863
+ return;
864
+ }
778
865
  const networkInspector = getNetworkInspector(client);
866
+ if (isRecordingEnabledRef.current) {
867
+ networkInspector.enable();
868
+ }
779
869
  return () => {
780
870
  networkInspector.dispose();
781
871
  };
782
- }, [client]);
872
+ }, [client, isHttpInspectorEnabled]);
783
873
  react.useEffect(() => {
784
- if (!client) {
874
+ if (!client || !isWebSocketInspectorEnabled) {
785
875
  return;
786
876
  }
787
877
  const eventsToForward = [
@@ -805,12 +895,15 @@ const useNetworkActivityDevTools = () => {
805
895
  client.onMessage("network-disable", () => {
806
896
  websocketInspector.disable();
807
897
  });
898
+ if (isRecordingEnabledRef.current) {
899
+ websocketInspector.enable();
900
+ }
808
901
  return () => {
809
902
  websocketInspector.dispose();
810
903
  };
811
- }, [client]);
904
+ }, [client, isWebSocketInspectorEnabled]);
812
905
  react.useEffect(() => {
813
- if (!client) {
906
+ if (!client || !isSSEInspectorEnabled) {
814
907
  return;
815
908
  }
816
909
  const eventsToForward = [
@@ -831,10 +924,13 @@ const useNetworkActivityDevTools = () => {
831
924
  client.onMessage("network-disable", () => {
832
925
  sseInspector.disable();
833
926
  });
927
+ if (isRecordingEnabledRef.current) {
928
+ sseInspector.enable();
929
+ }
834
930
  return () => {
835
931
  sseInspector.dispose();
836
932
  };
837
- }, [client]);
933
+ }, [client, isSSEInspectorEnabled]);
838
934
  return client;
839
935
  };
840
936
  exports.useNetworkActivityDevTools = useNetworkActivityDevTools;
@@ -1,4 +1,4 @@
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";
@@ -7,25 +7,27 @@ import { g as getEventSource } from "./event-source.js";
7
7
  function safeStringify(data) {
8
8
  try {
9
9
  return typeof data === "string" ? data : JSON.stringify(data);
10
- } catch (error) {
10
+ } catch {
11
11
  return String(data);
12
12
  }
13
13
  }
14
- function getHttpHeaderValue(headers, name) {
14
+ function getHttpHeader(headers, name) {
15
15
  const lowerName = name.toLowerCase();
16
16
  for (const key in headers) {
17
17
  if (key.toLowerCase() === lowerName) {
18
- return headers[key];
18
+ return { value: headers[key], originalKey: key };
19
19
  }
20
20
  }
21
21
  return void 0;
22
22
  }
23
23
  function getContentTypeMime(headers) {
24
- const contentType = getHttpHeaderValue(headers, "content-type");
25
- if (contentType) {
26
- return contentType.split(";")[0].trim();
24
+ const contentType = getHttpHeader(headers, "content-type");
25
+ if (!contentType) {
26
+ return void 0;
27
27
  }
28
- return void 0;
28
+ const { value } = contentType;
29
+ const actualValue = Array.isArray(value) ? value[0] : value;
30
+ return actualValue.split(";")[0].trim();
29
31
  }
30
32
  const getContentType = (request) => {
31
33
  const responseHeaders = request.responseHeaders;
@@ -227,6 +229,28 @@ const XHRInterceptor = {
227
229
  requestHeaderCallback = null;
228
230
  }
229
231
  };
232
+ const getStringSizeInBytes = (value) => {
233
+ return new TextEncoder().encode(value).length;
234
+ };
235
+ const splitSetCookieHeaderByComma = (header) => {
236
+ const regex = /(?:^|,\s)([^=;,]+=[^;]+(?:;[^,]*)*)/g;
237
+ const matches = [];
238
+ let match;
239
+ while ((match = regex.exec(header)) !== null) {
240
+ matches.push(match[1].trim());
241
+ }
242
+ return matches;
243
+ };
244
+ const applyReactNativeResponseHeadersLogic = (headers) => {
245
+ const parsedHeaders = { ...headers };
246
+ const setCookieHeader = getHttpHeader(headers, "set-cookie");
247
+ if (setCookieHeader) {
248
+ const { value, originalKey } = setCookieHeader;
249
+ const cookies = splitSetCookieHeaderByComma(value);
250
+ parsedHeaders[originalKey] = cookies.length > 0 ? cookies : value;
251
+ }
252
+ return parsedHeaders;
253
+ };
230
254
  const networkRequestsRegistry = getNetworkRequestsRegistry();
231
255
  function getRequestBody(body) {
232
256
  if (body === null || body === void 0) {
@@ -262,13 +286,27 @@ function getRequestBody(body) {
262
286
  };
263
287
  }
264
288
  const getResponseSize = (request) => {
265
- if (request.response === null) {
289
+ try {
290
+ const { responseType, response } = request;
291
+ if (response === null) {
292
+ return 0;
293
+ }
294
+ if (responseType === "" || responseType === "text") {
295
+ return getStringSizeInBytes(request.responseText);
296
+ }
297
+ if (responseType === "json") {
298
+ return getStringSizeInBytes(safeStringify(response));
299
+ }
300
+ if (responseType === "blob") {
301
+ return response.size;
302
+ }
303
+ if (responseType === "arraybuffer") {
304
+ return response.byteLength;
305
+ }
266
306
  return 0;
307
+ } catch {
308
+ return null;
267
309
  }
268
- if (typeof request.response === "object") {
269
- return request.response.size;
270
- }
271
- return request.response.length || 0;
272
310
  };
273
311
  const getResponseBody = async (request) => {
274
312
  const responseType = request.responseType;
@@ -287,6 +325,9 @@ const getResponseBody = async (request) => {
287
325
  });
288
326
  }
289
327
  }
328
+ if (responseType === "json") {
329
+ return safeStringify(request.response);
330
+ }
290
331
  return null;
291
332
  };
292
333
  const getInitiatorFromStack = () => {
@@ -347,7 +388,9 @@ const getNetworkInspector = (pluginClient) => {
347
388
  url: request._url,
348
389
  status: request.status,
349
390
  statusText: request.statusText,
350
- headers: request.responseHeaders || {},
391
+ headers: applyReactNativeResponseHeadersLogic(
392
+ request.responseHeaders || {}
393
+ ),
351
394
  contentType: getContentType(request),
352
395
  size: getResponseSize(request),
353
396
  responseTime: Date.now()
@@ -758,26 +801,73 @@ const getSSEInspector = () => {
758
801
  },
759
802
  isEnabled: () => SSEInterceptor.isInterceptorEnabled(),
760
803
  dispose: () => {
804
+ SSEInterceptor.disableInterception();
761
805
  eventEmitter.events = {};
762
806
  },
763
807
  on: (event, callback) => eventEmitter.on(event, callback)
764
808
  };
765
809
  };
766
- const useNetworkActivityDevTools = () => {
810
+ const DEFAULT_CONFIG = {
811
+ inspectors: {
812
+ http: true,
813
+ websocket: true,
814
+ sse: true
815
+ }
816
+ };
817
+ const validateConfig = (config) => {
818
+ const inspectors = config.inspectors;
819
+ if (!inspectors) {
820
+ return;
821
+ }
822
+ if (inspectors.sse && !inspectors.http) {
823
+ throw new Error("SSE inspector requires HTTP inspector to be enabled.");
824
+ }
825
+ };
826
+ const useNetworkActivityDevTools = (config = DEFAULT_CONFIG) => {
827
+ var _a, _b, _c;
828
+ const isRecordingEnabledRef = useRef(false);
767
829
  const client = useRozeniteDevToolsClient({
768
830
  pluginId: "@rozenite/network-activity-plugin"
769
831
  });
832
+ const isHttpInspectorEnabled = ((_a = config.inspectors) == null ? void 0 : _a.http) ?? true;
833
+ const isWebSocketInspectorEnabled = ((_b = config.inspectors) == null ? void 0 : _b.websocket) ?? true;
834
+ const isSSEInspectorEnabled = ((_c = config.inspectors) == null ? void 0 : _c.sse) ?? true;
835
+ useEffect(() => {
836
+ if (!client) {
837
+ return;
838
+ }
839
+ validateConfig(config);
840
+ }, [config]);
770
841
  useEffect(() => {
771
842
  if (!client) {
772
843
  return;
773
844
  }
845
+ const subscriptions = [
846
+ client.onMessage("network-enable", () => {
847
+ isRecordingEnabledRef.current = true;
848
+ }),
849
+ client.onMessage("network-disable", () => {
850
+ isRecordingEnabledRef.current = false;
851
+ })
852
+ ];
853
+ return () => {
854
+ subscriptions.forEach((subscription) => subscription.remove());
855
+ };
856
+ }, [client]);
857
+ useEffect(() => {
858
+ if (!client || !isHttpInspectorEnabled) {
859
+ return;
860
+ }
774
861
  const networkInspector = getNetworkInspector(client);
862
+ if (isRecordingEnabledRef.current) {
863
+ networkInspector.enable();
864
+ }
775
865
  return () => {
776
866
  networkInspector.dispose();
777
867
  };
778
- }, [client]);
868
+ }, [client, isHttpInspectorEnabled]);
779
869
  useEffect(() => {
780
- if (!client) {
870
+ if (!client || !isWebSocketInspectorEnabled) {
781
871
  return;
782
872
  }
783
873
  const eventsToForward = [
@@ -801,12 +891,15 @@ const useNetworkActivityDevTools = () => {
801
891
  client.onMessage("network-disable", () => {
802
892
  websocketInspector.disable();
803
893
  });
894
+ if (isRecordingEnabledRef.current) {
895
+ websocketInspector.enable();
896
+ }
804
897
  return () => {
805
898
  websocketInspector.dispose();
806
899
  };
807
- }, [client]);
900
+ }, [client, isWebSocketInspectorEnabled]);
808
901
  useEffect(() => {
809
- if (!client) {
902
+ if (!client || !isSSEInspectorEnabled) {
810
903
  return;
811
904
  }
812
905
  const eventsToForward = [
@@ -827,10 +920,13 @@ const useNetworkActivityDevTools = () => {
827
920
  client.onMessage("network-disable", () => {
828
921
  sseInspector.disable();
829
922
  });
923
+ if (isRecordingEnabledRef.current) {
924
+ sseInspector.enable();
925
+ }
830
926
  return () => {
831
927
  sseInspector.dispose();
832
928
  };
833
- }, [client]);
929
+ }, [client, isSSEInspectorEnabled]);
834
930
  return client;
835
931
  };
836
932
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/network-activity-plugin",
3
- "version": "1.0.0-alpha.11",
3
+ "version": "1.0.0-alpha.12",
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.11"
11
+ "@rozenite/plugin-bridge": "1.0.0-alpha.12"
12
12
  },
13
13
  "devDependencies": {
14
14
  "@floating-ui/react": "^0.26.0",
@@ -36,8 +36,8 @@
36
36
  "zustand": "^5.0.6",
37
37
  "@types/react": "~18.3.23",
38
38
  "@types/react-dom": "~18.3.1",
39
- "rozenite": "1.0.0-alpha.11",
40
- "@rozenite/vite-plugin": "1.0.0-alpha.11"
39
+ "@rozenite/vite-plugin": "1.0.0-alpha.12",
40
+ "rozenite": "1.0.0-alpha.12"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "react-native-sse": "*"