@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
@@ -2,12 +2,23 @@ import { useEffect, useRef } from 'react';
2
2
  import { ScrollArea } from '../components/ScrollArea';
3
3
  import { JsonTree } from '../components/JsonTree';
4
4
  import { HttpNetworkEntry } from '../state/model';
5
+ import { Section } from '../components/Section';
6
+ import { KeyValueGrid } from '../components/KeyValueGrid';
7
+ import { CodeBlock } from '../components/CodeBlock';
5
8
 
6
9
  export type ResponseTabProps = {
7
10
  selectedRequest: HttpNetworkEntry;
8
11
  onRequestResponseBody: (requestId: string) => void;
9
12
  };
10
13
 
14
+ const renderResponseBodySection = (children: React.ReactNode) => {
15
+ return (
16
+ <Section title="Response Body" collapsible={false}>
17
+ <div className="space-y-4">{children}</div>
18
+ </Section>
19
+ );
20
+ };
21
+
11
22
  export const ResponseTab = ({
12
23
  selectedRequest,
13
24
  onRequestResponseBody,
@@ -24,10 +35,10 @@ export const ResponseTab = ({
24
35
  }
25
36
  }, [selectedRequest.id]);
26
37
 
27
- const renderResponseBody = () => {
28
- const responseBody = selectedRequest.response?.body;
38
+ const responseBody = selectedRequest.response?.body;
29
39
 
30
- if (!responseBody) {
40
+ const renderResponseBody = () => {
41
+ if (!responseBody || responseBody.data === null) {
31
42
  return (
32
43
  <div className="text-sm text-gray-400">
33
44
  No response body available for this request
@@ -37,94 +48,68 @@ export const ResponseTab = ({
37
48
 
38
49
  const { type, data } = responseBody;
39
50
 
40
- // Handle null data
41
- if (data === null) {
42
- return (
43
- <div className="text-sm text-gray-400">
44
- No response body available for this request
45
- </div>
46
- );
47
- }
51
+ const contentTypeGrid = (
52
+ <KeyValueGrid
53
+ items={[
54
+ {
55
+ key: 'Content-Type',
56
+ value: type,
57
+ valueClassName: 'text-blue-400',
58
+ },
59
+ ]}
60
+ />
61
+ );
62
+
63
+ if (type.startsWith('application/json')) {
64
+ let bodyContent;
48
65
 
49
- // Handle JSON content
50
- if (type === 'application/json') {
51
66
  try {
52
67
  const jsonData = JSON.parse(data);
53
- return (
54
- <div className="space-y-4">
55
- <div className="text-sm mb-2">
56
- <span className="text-gray-400">Content-Type: </span>
57
- <span className="text-blue-400">{type}</span>
58
- </div>
59
- <div className="bg-gray-800 p-3 rounded border border-gray-700">
60
- <JsonTree data={jsonData} />
61
- </div>
62
- </div>
68
+
69
+ bodyContent = (
70
+ <CodeBlock>
71
+ <JsonTree data={jsonData} />
72
+ </CodeBlock>
63
73
  );
64
74
  } catch {
65
- // Fallback to pre tag if JSON parsing fails
66
- return (
67
- <div className="space-y-4">
68
- <div className="text-sm mb-2">
69
- <span className="text-gray-400">Content-Type: </span>
70
- <span className="text-blue-400">{type}</span>
71
- </div>
72
- <pre className="text-sm font-mono text-gray-300 whitespace-pre-wrap bg-gray-800 p-3 rounded border border-gray-700 overflow-x-auto">
73
- {data}
74
- </pre>
75
- <div className="text-xs text-gray-500">
75
+ bodyContent = (
76
+ <>
77
+ <CodeBlock>{data}</CodeBlock>
78
+ <div className="text-xs text-gray-500 mt-1">
76
79
  ⚠️ Failed to parse as JSON, showing as raw text
77
80
  </div>
78
- </div>
81
+ </>
79
82
  );
80
83
  }
81
- }
82
84
 
83
- // Handle HTML content
84
- if (type === 'text/html') {
85
- return (
86
- <div className="space-y-4">
87
- <div className="text-sm mb-2">
88
- <span className="text-gray-400">Content-Type: </span>
89
- <span className="text-blue-400">{type}</span>
90
- </div>
91
- <pre className="text-sm font-mono text-gray-300 whitespace-pre-wrap bg-gray-800 p-3 rounded border border-gray-700 overflow-x-auto">
92
- {data}
93
- </pre>
94
- </div>
85
+ return renderResponseBodySection(
86
+ <>
87
+ {contentTypeGrid}
88
+ {bodyContent}
89
+ </>
95
90
  );
96
91
  }
97
92
 
98
- // Handle other text content types
99
93
  if (
100
94
  type.startsWith('text/') ||
101
- type === 'application/xml' ||
102
- type === 'application/javascript'
95
+ type.startsWith('application/xml') ||
96
+ type.startsWith('application/javascript')
103
97
  ) {
104
- return (
105
- <div className="space-y-4">
106
- <div className="text-sm mb-2">
107
- <span className="text-gray-400">Content-Type: </span>
108
- <span className="text-blue-400">{type}</span>
109
- </div>
110
- <pre className="text-sm font-mono text-gray-300 whitespace-pre-wrap bg-gray-800 p-3 rounded border border-gray-700 overflow-x-auto">
111
- {data}
112
- </pre>
113
- </div>
98
+ return renderResponseBodySection(
99
+ <>
100
+ {contentTypeGrid}
101
+ <CodeBlock>{data}</CodeBlock>
102
+ </>
114
103
  );
115
104
  }
116
105
 
117
- // Handle other content types
118
- return (
119
- <div className="space-y-4">
120
- <div className="text-sm mb-2">
121
- <span className="text-gray-400">Content-Type: </span>
122
- <span className="text-blue-400">{type}</span>
123
- </div>
106
+ return renderResponseBodySection(
107
+ <>
108
+ {contentTypeGrid}
124
109
  <div className="text-sm text-gray-400">
125
110
  Binary content not shown - {data.length} bytes
126
111
  </div>
127
- </div>
112
+ </>
128
113
  );
129
114
  };
130
115
 
@@ -1,5 +1,5 @@
1
1
  import { HttpHeaders, RequestPostData } from '../shared/client';
2
- import { getHttpHeaderValue } from './getHttpHeaderValue';
2
+ import { getHttpHeader } from './getHttpHeader';
3
3
  import { inferContentTypeFromPostData } from './inferContentTypeFromPostData';
4
4
 
5
5
  /**
@@ -12,7 +12,7 @@ export function applyReactNativeRequestHeadersLogic(
12
12
  headers: HttpHeaders,
13
13
  postData?: RequestPostData
14
14
  ): HttpHeaders {
15
- const existingContentType = getHttpHeaderValue(headers, 'content-type');
15
+ const existingContentType = getHttpHeader(headers, 'content-type');
16
16
 
17
17
  if (existingContentType) {
18
18
  return headers;
@@ -0,0 +1,29 @@
1
+ import { HttpHeaders, XHRHeaders } from '../shared/client';
2
+ import { splitSetCookieHeaderByComma } from './cookieParser';
3
+ import { getHttpHeader } from './getHttpHeader';
4
+
5
+ /**
6
+ * Applies React Native specific logic to response headers.
7
+ * React Native concatenates multiple header values into single strings,
8
+ * this function parses them back into arrays where appropriate.
9
+ *
10
+ * See:
11
+ * https://github.com/facebook/react-native/blob/588f0c5ce6c283f116228456da2170d2adc3cbf4/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java#L637
12
+ */
13
+ export const applyReactNativeResponseHeadersLogic = (
14
+ headers: XHRHeaders
15
+ ): HttpHeaders => {
16
+ const parsedHeaders: HttpHeaders = { ...headers };
17
+
18
+ const setCookieHeader = getHttpHeader(headers, 'set-cookie');
19
+
20
+ if (setCookieHeader) {
21
+ const { value, originalKey } = setCookieHeader;
22
+
23
+ const cookies = splitSetCookieHeaderByComma(value);
24
+
25
+ parsedHeaders[originalKey] = cookies.length > 0 ? cookies : value;
26
+ }
27
+
28
+ return parsedHeaders;
29
+ };
@@ -0,0 +1,126 @@
1
+ import { Cookie, HttpHeaders } from '../shared/client';
2
+ import { getHttpHeader } from './getHttpHeader';
3
+
4
+ export const parseSetCookieHeader = (setCookieStr: string): Cookie => {
5
+ const parts = setCookieStr.split(';').map((p) => p.trim());
6
+ const [nameValue, ...attributes] = parts;
7
+ const [name, ...valueParts] = nameValue.split('=');
8
+
9
+ const value = valueParts.join('=');
10
+
11
+ const cookieObj: Cookie = {
12
+ name: name?.trim() || '',
13
+ value: value?.trim() || '',
14
+ };
15
+
16
+ attributes.forEach((attr) => {
17
+ const [attrName, ...attrValueParts] = attr.split('=');
18
+ const lowerAttrName = attrName.trim().toLowerCase();
19
+ const attrValue = attrValueParts.join('=');
20
+
21
+ switch (lowerAttrName) {
22
+ case 'domain':
23
+ cookieObj.domain = attrValue;
24
+
25
+ break;
26
+
27
+ case 'path':
28
+ cookieObj.path = attrValue;
29
+
30
+ break;
31
+
32
+ case 'expires':
33
+ cookieObj.expires = attrValue;
34
+
35
+ break;
36
+
37
+ case 'max-age':
38
+ cookieObj.maxAge = attrValue;
39
+
40
+ break;
41
+
42
+ case 'secure':
43
+ cookieObj.secure = true;
44
+
45
+ break;
46
+
47
+ case 'httponly':
48
+ cookieObj.httpOnly = true;
49
+
50
+ break;
51
+
52
+ case 'samesite':
53
+ cookieObj.sameSite = attrValue;
54
+
55
+ break;
56
+ }
57
+ });
58
+
59
+ return cookieObj;
60
+ };
61
+
62
+ export const splitSetCookieHeaderByComma = (header: string): string[] => {
63
+ const regex = /(?:^|,\s)([^=;,]+=[^;]+(?:;[^,]*)*)/g;
64
+ const matches: string[] = [];
65
+
66
+ let match;
67
+
68
+ while ((match = regex.exec(header)) !== null) {
69
+ matches.push(match[1].trim());
70
+ }
71
+
72
+ return matches;
73
+ };
74
+
75
+ export const parseCookieHeader = (cookieString: string): Cookie[] => {
76
+ if (!cookieString) return [];
77
+
78
+ return cookieString
79
+ .split(';')
80
+ .map((cookieStr) => {
81
+ const [name, ...valueParts] = cookieStr.trim().split('=');
82
+ const value = valueParts.join('=');
83
+
84
+ return {
85
+ name: name?.trim() || '',
86
+ value: value?.trim() || '',
87
+ };
88
+ })
89
+ .filter((cookieObj) => cookieObj.name);
90
+ };
91
+
92
+ export const parseRequestCookiesFromHeaders = (
93
+ headers: HttpHeaders
94
+ ): Cookie[] => {
95
+ const cookieHeader = getHttpHeader(headers, 'cookie');
96
+
97
+ if (!cookieHeader) {
98
+ return [];
99
+ }
100
+
101
+ const { value } = cookieHeader;
102
+
103
+ if (Array.isArray(value)) {
104
+ return value.flatMap(parseCookieHeader);
105
+ }
106
+
107
+ return parseCookieHeader(value);
108
+ };
109
+
110
+ export const parseResponseCookiesFromHeaders = (
111
+ headers: HttpHeaders
112
+ ): Cookie[] => {
113
+ const setCookieHeader = getHttpHeader(headers, 'set-cookie');
114
+
115
+ if (!setCookieHeader) {
116
+ return [];
117
+ }
118
+
119
+ const { value } = setCookieHeader;
120
+
121
+ if (Array.isArray(value)) {
122
+ return value.flatMap(parseSetCookieHeader);
123
+ }
124
+
125
+ return [parseSetCookieHeader(value)];
126
+ };
@@ -1,12 +1,17 @@
1
1
  import { HttpHeaders } from '../shared/client';
2
- import { getHttpHeaderValue } from './getHttpHeaderValue';
2
+ import { getHttpHeader } from './getHttpHeader';
3
3
 
4
4
  export function getContentTypeMime(headers: HttpHeaders) {
5
- const contentType = getHttpHeaderValue(headers, 'content-type');
5
+ const contentType = getHttpHeader(headers, 'content-type');
6
6
 
7
- if (contentType) {
8
- return contentType.split(';')[0].trim();
7
+ if (!contentType) {
8
+ return undefined;
9
9
  }
10
10
 
11
- return undefined;
11
+ const { value } = contentType;
12
+
13
+ // Content-Type can't be an array, but if it does we simply get the first element.
14
+ const actualValue = Array.isArray(value) ? value[0] : value;
15
+
16
+ return actualValue.split(';')[0].trim();
12
17
  }
@@ -0,0 +1,17 @@
1
+ import { HttpHeaders, XHRHeaders } from '../shared/client';
2
+
3
+ // Utility to get header value and actual key case-insensitively
4
+ export function getHttpHeader<T extends HttpHeaders | XHRHeaders>(
5
+ headers: T,
6
+ name: string
7
+ ) {
8
+ const lowerName = name.toLowerCase();
9
+
10
+ for (const key in headers) {
11
+ if (key.toLowerCase() === lowerName) {
12
+ return { value: headers[key], originalKey: key };
13
+ }
14
+ }
15
+
16
+ return undefined;
17
+ }
@@ -0,0 +1,3 @@
1
+ export const getStringSizeInBytes = (value: string) => {
2
+ return new TextEncoder().encode(value).length;
3
+ };
@@ -0,0 +1,3 @@
1
+ export const isNumber = (value: unknown): value is number => {
2
+ return typeof value === 'number';
3
+ };
@@ -1,7 +1,7 @@
1
1
  export function safeStringify(data: unknown): string {
2
2
  try {
3
3
  return typeof data === 'string' ? data : JSON.stringify(data);
4
- } catch (error) {
4
+ } catch {
5
5
  return String(data);
6
6
  }
7
7
  }
@@ -1,2 +0,0 @@
1
- import { HttpHeaders } from '../shared/client';
2
- export declare function getHttpHeaderValue(headers: HttpHeaders, name: string): string | undefined;
@@ -1,14 +0,0 @@
1
- import { HttpHeaders } from '../shared/client';
2
-
3
- // Utility to get header value case-insensitively
4
- export function getHttpHeaderValue(headers: HttpHeaders, name: string) {
5
- const lowerName = name.toLowerCase();
6
-
7
- for (const key in headers) {
8
- if (key.toLowerCase() === lowerName) {
9
- return headers[key];
10
- }
11
- }
12
-
13
- return undefined;
14
- }