@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.
- package/README.md +2 -0
- package/dist/App.html +2 -2
- package/dist/assets/{App-Ct73Yrm6.css → App-DCuHdq4D.css} +17 -0
- package/dist/assets/{App-BKBLGSeM.js → App-JuOeT_VQ.js} +2693 -2642
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/config.d.ts +13 -0
- package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -1
- package/dist/src/shared/client.d.ts +15 -3
- package/dist/src/ui/components/Button.d.ts +1 -1
- package/dist/src/ui/components/CodeBlock.d.ts +3 -0
- package/dist/src/ui/components/CookieCard.d.ts +7 -0
- package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +1 -1
- package/dist/src/ui/components/Section.d.ts +2 -1
- package/dist/src/ui/state/model.d.ts +4 -4
- package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +10 -0
- package/dist/src/utils/cookieParser.d.ts +6 -0
- package/dist/src/utils/getHttpHeader.d.ts +5 -0
- package/dist/src/utils/getStringSizeInBytes.d.ts +1 -0
- package/dist/src/utils/isNumber.d.ts +1 -0
- package/dist/useNetworkActivityDevTools.cjs +115 -19
- package/dist/useNetworkActivityDevTools.js +116 -20
- package/package.json +4 -4
- package/src/react-native/config.ts +33 -0
- package/src/react-native/http/network-inspector.ts +36 -10
- package/src/react-native/sse/sse-inspector.ts +1 -0
- package/src/react-native/useNetworkActivityDevTools.ts +63 -8
- package/src/shared/client.ts +17 -3
- package/src/ui/components/CodeBlock.tsx +19 -0
- package/src/ui/components/CookieCard.tsx +64 -0
- package/src/ui/components/JsonTree.tsx +10 -3
- package/src/ui/components/JsonTreeCopyableItem.tsx +14 -10
- package/src/ui/components/RequestList.tsx +15 -5
- package/src/ui/components/Section.tsx +31 -4
- package/src/ui/state/model.ts +4 -4
- package/src/ui/tabs/CookiesTab.tsx +64 -263
- package/src/ui/tabs/HeadersTab.tsx +26 -20
- package/src/ui/tabs/RequestTab.tsx +62 -47
- package/src/ui/tabs/ResponseTab.tsx +54 -69
- package/src/utils/applyReactNativeRequestHeadersLogic.ts +2 -2
- package/src/utils/applyReactNativeResponseHeadersLogic.ts +29 -0
- package/src/utils/cookieParser.ts +126 -0
- package/src/utils/getContentTypeMimeType.ts +10 -5
- package/src/utils/getHttpHeader.ts +17 -0
- package/src/utils/getStringSizeInBytes.ts +3 -0
- package/src/utils/isNumber.ts +3 -0
- package/src/utils/safeStringify.ts +1 -1
- package/dist/src/utils/getHttpHeaderValue.d.ts +0 -2
- package/src/utils/getHttpHeaderValue.ts +0 -14
package/dist/rozenite.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@rozenite/network-activity-plugin","version":"1.0.0-alpha.
|
|
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
|
-
|
|
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" |
|
|
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> {
|
|
@@ -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:
|
|
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:
|
|
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
|
|
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 @@
|
|
|
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
|
|
14
|
+
} catch {
|
|
15
15
|
return String(data);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
function
|
|
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 =
|
|
29
|
-
if (contentType) {
|
|
30
|
-
return
|
|
28
|
+
const contentType = getHttpHeader(headers, "content-type");
|
|
29
|
+
if (!contentType) {
|
|
30
|
+
return void 0;
|
|
31
31
|
}
|
|
32
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
10
|
+
} catch {
|
|
11
11
|
return String(data);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
function
|
|
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 =
|
|
25
|
-
if (contentType) {
|
|
26
|
-
return
|
|
24
|
+
const contentType = getHttpHeader(headers, "content-type");
|
|
25
|
+
if (!contentType) {
|
|
26
|
+
return void 0;
|
|
27
27
|
}
|
|
28
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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
|
+
"@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.
|
|
40
|
-
"
|
|
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": "*"
|