@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
@@ -0,0 +1,33 @@
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
+ };
13
+
14
+ export const DEFAULT_CONFIG: NetworkActivityDevToolsConfig = {
15
+ inspectors: {
16
+ http: true,
17
+ websocket: true,
18
+ sse: true,
19
+ },
20
+ };
21
+
22
+ export const validateConfig = (config: NetworkActivityDevToolsConfig): void => {
23
+ const inspectors = config.inspectors;
24
+
25
+ if (!inspectors) {
26
+ return;
27
+ }
28
+
29
+ // For SSE, HTTP must be enabled
30
+ if (inspectors.sse && !inspectors.http) {
31
+ throw new Error('SSE inspector requires HTTP inspector to be enabled.');
32
+ }
33
+ };
@@ -10,6 +10,8 @@ import { getNetworkRequestsRegistry } from './network-requests-registry';
10
10
  import { getBlobName } from '../utils/getBlobName';
11
11
  import { getFormDataEntries } from '../utils/getFormDataEntries';
12
12
  import { XHRInterceptor } from './xhr-interceptor';
13
+ import { getStringSizeInBytes } from '../../utils/getStringSizeInBytes';
14
+ import { applyReactNativeResponseHeadersLogic } from '../../utils/applyReactNativeResponseHeadersLogic';
13
15
 
14
16
  const networkRequestsRegistry = getNetworkRequestsRegistry();
15
17
 
@@ -51,17 +53,35 @@ function getRequestBody(body: XHRPostData): RequestPostData {
51
53
  };
52
54
  }
53
55
 
54
- const getResponseSize = (request: XMLHttpRequest): number => {
55
- // Handle a case of 204 where no-content was sent.
56
- if (request.response === null) {
57
- return 0;
58
- }
56
+ const getResponseSize = (request: XMLHttpRequest): number | null => {
57
+ try {
58
+ const { responseType, response } = request;
59
59
 
60
- if (typeof request.response === 'object') {
61
- return request.response.size;
62
- }
60
+ // Handle a case of 204 where no-content was sent.
61
+ if (response === null) {
62
+ return 0;
63
+ }
64
+
65
+ if (responseType === '' || responseType === 'text') {
66
+ return getStringSizeInBytes(request.responseText);
67
+ }
68
+
69
+ if (responseType === 'json') {
70
+ return getStringSizeInBytes(safeStringify(response));
71
+ }
63
72
 
64
- return request.response.length || 0;
73
+ if (responseType === 'blob') {
74
+ return response.size;
75
+ }
76
+
77
+ if (responseType === 'arraybuffer') {
78
+ return response.byteLength;
79
+ }
80
+
81
+ return 0;
82
+ } catch {
83
+ return null;
84
+ }
65
85
  };
66
86
 
67
87
  const getResponseBody = async (
@@ -93,6 +113,10 @@ const getResponseBody = async (
93
113
  }
94
114
  }
95
115
 
116
+ if (responseType === 'json') {
117
+ return safeStringify(request.response);
118
+ }
119
+
96
120
  return null;
97
121
  };
98
122
 
@@ -184,7 +208,9 @@ export const getNetworkInspector = (
184
208
  url: request._url as string,
185
209
  status: request.status,
186
210
  statusText: request.statusText,
187
- headers: request.responseHeaders || {},
211
+ headers: applyReactNativeResponseHeadersLogic(
212
+ request.responseHeaders || {}
213
+ ),
188
214
  contentType: getContentType(request),
189
215
  size: getResponseSize(request),
190
216
  responseTime: Date.now(),
@@ -110,6 +110,7 @@ export const getSSEInspector = (): SSEInspector => {
110
110
  },
111
111
  isEnabled: () => SSEInterceptor.isInterceptorEnabled(),
112
112
  dispose: () => {
113
+ SSEInterceptor.disableInterception();
113
114
  eventEmitter.events = {};
114
115
  },
115
116
  on: <TEventType extends keyof SSEEventMap>(
@@ -1,4 +1,4 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
2
  import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
3
3
  import { getNetworkInspector } from './http/network-inspector';
4
4
  import { NetworkActivityEventMap } from '../shared/client';
@@ -7,26 +7,71 @@ import { WebSocketEventMap } from '../shared/websocket-events';
7
7
  import { UnionToTuple } from './utils';
8
8
  import { getSSEInspector } from './sse/sse-inspector';
9
9
  import { SSEEventMap } from '../shared/sse-events';
10
-
11
- export const useNetworkActivityDevTools = () => {
10
+ import {
11
+ DEFAULT_CONFIG,
12
+ NetworkActivityDevToolsConfig,
13
+ validateConfig,
14
+ } from './config';
15
+
16
+ export const useNetworkActivityDevTools = (
17
+ config: NetworkActivityDevToolsConfig = DEFAULT_CONFIG
18
+ ) => {
19
+ const isRecordingEnabledRef = useRef(false);
12
20
  const client = useRozeniteDevToolsClient<NetworkActivityEventMap>({
13
21
  pluginId: '@rozenite/network-activity-plugin',
14
22
  });
15
23
 
24
+ const isHttpInspectorEnabled = config.inspectors?.http ?? true;
25
+ const isWebSocketInspectorEnabled = config.inspectors?.websocket ?? true;
26
+ const isSSEInspectorEnabled = config.inspectors?.sse ?? true;
27
+
28
+ useEffect(() => {
29
+ if (!client) {
30
+ return;
31
+ }
32
+
33
+ validateConfig(config);
34
+ }, [config]);
35
+
36
+ /** Persist the recording state across hot reloads */
16
37
  useEffect(() => {
17
38
  if (!client) {
18
39
  return;
19
40
  }
20
41
 
42
+ const subscriptions = [
43
+ client.onMessage('network-enable', () => {
44
+ isRecordingEnabledRef.current = true;
45
+ }),
46
+ client.onMessage('network-disable', () => {
47
+ isRecordingEnabledRef.current = false;
48
+ }),
49
+ ];
50
+
51
+ return () => {
52
+ subscriptions.forEach((subscription) => subscription.remove());
53
+ };
54
+ }, [client]);
55
+
56
+ useEffect(() => {
57
+ if (!client || !isHttpInspectorEnabled) {
58
+ return;
59
+ }
60
+
21
61
  const networkInspector = getNetworkInspector(client);
22
62
 
63
+ // If recording was previously enabled, enable the inspector (hot reload)
64
+ if (isRecordingEnabledRef.current) {
65
+ networkInspector.enable();
66
+ }
67
+
23
68
  return () => {
24
69
  networkInspector.dispose();
25
70
  };
26
- }, [client]);
71
+ }, [client, isHttpInspectorEnabled]);
27
72
 
28
73
  useEffect(() => {
29
- if (!client) {
74
+ if (!client || !isWebSocketInspectorEnabled) {
30
75
  return;
31
76
  }
32
77
 
@@ -55,14 +100,19 @@ export const useNetworkActivityDevTools = () => {
55
100
  websocketInspector.disable();
56
101
  });
57
102
 
103
+ // If recording was previously enabled, enable the inspector (hot reload)
104
+ if (isRecordingEnabledRef.current) {
105
+ websocketInspector.enable();
106
+ }
107
+
58
108
  return () => {
59
109
  // Subscriptions will be disposed by the inspector
60
110
  websocketInspector.dispose();
61
111
  };
62
- }, [client]);
112
+ }, [client, isWebSocketInspectorEnabled]);
63
113
 
64
114
  useEffect(() => {
65
- if (!client) {
115
+ if (!client || !isSSEInspectorEnabled) {
66
116
  return;
67
117
  }
68
118
 
@@ -88,11 +138,16 @@ export const useNetworkActivityDevTools = () => {
88
138
  sseInspector.disable();
89
139
  });
90
140
 
141
+ // If recording was previously enabled, enable the inspector (hot reload)
142
+ if (isRecordingEnabledRef.current) {
143
+ sseInspector.enable();
144
+ }
145
+
91
146
  return () => {
92
147
  // Subscriptions will be disposed by the inspector
93
148
  sseInspector.dispose();
94
149
  };
95
- }, [client]);
150
+ }, [client, isSSEInspectorEnabled]);
96
151
 
97
152
  return client;
98
153
  };
@@ -2,7 +2,9 @@ import { RozeniteDevToolsClient } from '@rozenite/plugin-bridge';
2
2
  import { WebSocketEventMap } from './websocket-events';
3
3
  import { SSEEventMap } from './sse-events';
4
4
 
5
- export type HttpHeaders = Record<string, string>;
5
+ export type HttpHeaders = Record<string, string | string[]>;
6
+ export type XHRHeaders = NonNullable<XMLHttpRequest['responseHeaders']>;
7
+
6
8
  export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
7
9
 
8
10
  export type RequestId = string;
@@ -28,6 +30,18 @@ export type RequestPostData =
28
30
  | null
29
31
  | undefined;
30
32
 
33
+ export type Cookie = {
34
+ name: string;
35
+ value: string;
36
+ domain?: string;
37
+ path?: string;
38
+ expires?: string;
39
+ maxAge?: string;
40
+ secure?: boolean;
41
+ httpOnly?: boolean;
42
+ sameSite?: string;
43
+ };
44
+
31
45
  export type Request = {
32
46
  url: string;
33
47
  method: HttpMethod;
@@ -41,7 +55,7 @@ export type Response = {
41
55
  statusText: string;
42
56
  headers: HttpHeaders;
43
57
  contentType: string;
44
- size: number;
58
+ size: number | null;
45
59
  responseTime: Timestamp;
46
60
  };
47
61
 
@@ -79,7 +93,7 @@ export type NetworkActivityEventMap = {
79
93
  requestId: RequestId;
80
94
  timestamp: Timestamp;
81
95
  duration: number;
82
- size: number;
96
+ size: number | null;
83
97
  ttfb: number;
84
98
  };
85
99
 
@@ -0,0 +1,19 @@
1
+ import { HTMLProps } from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export type CodeBlockProps = HTMLProps<HTMLPreElement>;
5
+
6
+ const codeBlockClassNames =
7
+ 'text-sm font-mono text-gray-300 whitespace-pre-wrap bg-gray-800 p-3 rounded border border-gray-700 overflow-x-auto wrap-anywhere';
8
+
9
+ export const CodeBlock = ({
10
+ children,
11
+ className,
12
+ ...props
13
+ }: CodeBlockProps) => {
14
+ return (
15
+ <pre className={cn(codeBlockClassNames, className)} {...props}>
16
+ {children}
17
+ </pre>
18
+ );
19
+ };
@@ -0,0 +1,64 @@
1
+ import { Badge } from './Badge';
2
+ import { Cookie } from '../../shared/client';
3
+ import { cn } from '../utils/cn';
4
+
5
+ type CookieCardProps = {
6
+ cookie: Cookie;
7
+ keyClassName?: string;
8
+ };
9
+
10
+ export const CookieCard = ({ cookie, keyClassName }: CookieCardProps) => (
11
+ <div className="bg-gray-800 border border-gray-700 rounded p-3">
12
+ <div className="flex items-center justify-between mb-2">
13
+ <span className={cn('text-sm font-medium', keyClassName)}>
14
+ {cookie.name}
15
+ </span>
16
+ <div className="flex items-center gap-2">
17
+ {cookie.secure && (
18
+ <Badge
19
+ variant="outline"
20
+ className="text-xs text-yellow-400 border-yellow-400"
21
+ >
22
+ Secure
23
+ </Badge>
24
+ )}
25
+ {cookie.httpOnly && (
26
+ <Badge
27
+ variant="outline"
28
+ className="text-xs text-purple-400 border-purple-400"
29
+ >
30
+ HttpOnly
31
+ </Badge>
32
+ )}
33
+ </div>
34
+ </div>
35
+ <div className="text-sm text-gray-300 mb-2 break-all">{cookie.value}</div>
36
+ <div className="grid grid-cols-2 gap-4 text-xs text-gray-400">
37
+ {cookie.domain && (
38
+ <div>
39
+ <span className="font-medium">Domain:</span> {cookie.domain}
40
+ </div>
41
+ )}
42
+ {cookie.path && (
43
+ <div>
44
+ <span className="font-medium">Path:</span> {cookie.path}
45
+ </div>
46
+ )}
47
+ {cookie.expires && (
48
+ <div>
49
+ <span className="font-medium">Expires:</span> {cookie.expires}
50
+ </div>
51
+ )}
52
+ {cookie.maxAge && (
53
+ <div>
54
+ <span className="font-medium">Max-Age:</span> {cookie.maxAge}
55
+ </div>
56
+ )}
57
+ {cookie.sameSite && (
58
+ <div>
59
+ <span className="font-medium">SameSite:</span> {cookie.sameSite}
60
+ </div>
61
+ )}
62
+ </div>
63
+ </div>
64
+ );
@@ -35,13 +35,20 @@ export const JsonTree = ({
35
35
  shouldExpandNodeInitially={shouldExpandNodeInitially}
36
36
  // For objects and arrays
37
37
  getItemString={(_type, data, itemType, itemString) => (
38
- <JsonTreeCopyableItem getCopyableValue={() => JSON.stringify(data, null, 2)}>
39
- <>{itemType} {itemString}</>
38
+ <JsonTreeCopyableItem
39
+ getCopyableValue={() => JSON.stringify(data, null, 2)}
40
+ >
41
+ <>
42
+ {itemType} {itemString}
43
+ </>
40
44
  </JsonTreeCopyableItem>
41
45
  )}
42
46
  // For primitives
43
47
  valueRenderer={(valueAsString, value) => (
44
- <JsonTreeCopyableItem getCopyableValue={() => String(value)} className="ml-2">
48
+ <JsonTreeCopyableItem
49
+ getCopyableValue={() => String(value)}
50
+ className="ml-2"
51
+ >
45
52
  {String(valueAsString)}
46
53
  </JsonTreeCopyableItem>
47
54
  )}
@@ -1,33 +1,37 @@
1
- import { Check, Copy } from "lucide-react";
2
- import { MouseEvent, PropsWithChildren } from "react";
3
- import { useCopyToClipboard } from "../hooks/useCopyToClipboard";
4
- import { cn } from "../utils/cn";
1
+ import { Check, Copy } from 'lucide-react';
2
+ import { MouseEvent, PropsWithChildren } from 'react';
3
+ import { useCopyToClipboard } from '../hooks/useCopyToClipboard';
4
+ import { cn } from '../utils/cn';
5
5
 
6
6
  type JsonTreeCopyableItemProps = PropsWithChildren<{
7
7
  getCopyableValue: () => string;
8
8
  className?: string;
9
9
  }>;
10
10
 
11
- export const JsonTreeCopyableItem = ({ children, getCopyableValue, className }: JsonTreeCopyableItemProps) => {
11
+ export const JsonTreeCopyableItem = ({
12
+ children,
13
+ getCopyableValue,
14
+ className,
15
+ }: JsonTreeCopyableItemProps) => {
12
16
  const { isCopied, copy } = useCopyToClipboard();
13
-
17
+
14
18
  const handleCopy = (event: MouseEvent) => {
15
19
  event.stopPropagation();
16
20
 
17
21
  copy(getCopyableValue());
18
- }
22
+ };
19
23
 
20
24
  const Icon = isCopied ? Check : Copy;
21
25
 
22
26
  return (
23
27
  <span className={cn('inline-block group', className)}>
24
28
  {children}
25
- <div
29
+ <div
26
30
  className="inline-block cursor-pointer opacity-0 group-hover:opacity-100 text-gray-500 hover:text-gray-300 transition-all p-2 -m-2 ml-0 translate-y-0.75"
27
31
  onClick={handleCopy}
28
32
  >
29
- <Icon className='h-4 w-4' />
33
+ <Icon className="h-4 w-4" />
30
34
  </div>
31
35
  </span>
32
36
  );
33
- }
37
+ };
@@ -17,6 +17,7 @@ import {
17
17
  } from '../state/hooks';
18
18
  import { getStatusColor } from '../utils/getStatusColor';
19
19
  import { FilterState } from './FilterBar';
20
+ import { isNumber } from '../../utils/isNumber';
20
21
 
21
22
  type NetworkRequest = {
22
23
  id: RequestId;
@@ -134,7 +135,7 @@ const processNetworkRequests = (
134
135
  method: request.method,
135
136
  domain,
136
137
  path,
137
- size: formatSize(request.size || 0),
138
+ size: isNumber(request.size) ? formatSize(request.size) : '—',
138
139
  time: formatDuration(duration),
139
140
  type: request.type,
140
141
  startTime: formatStartTime(request.timestamp),
@@ -153,8 +154,13 @@ const columns = [
153
154
  }),
154
155
  columnHelper.accessor('name', {
155
156
  header: 'Name',
156
- cell: ({ getValue }) => (
157
- <div className="flex-1 min-w-0 truncate">{getValue()}</div>
157
+ cell: ({ row, getValue }) => (
158
+ <div
159
+ className="flex-1 min-w-0 truncate"
160
+ title={row.original.path}
161
+ >
162
+ {getValue()}
163
+ </div>
158
164
  ),
159
165
  sortingFn: 'alphanumeric',
160
166
  }),
@@ -184,13 +190,17 @@ const columns = [
184
190
  }),
185
191
  columnHelper.accessor('size', {
186
192
  header: 'Size',
187
- cell: ({ getValue }) => <div className="text-gray-300">{getValue()}</div>,
193
+ cell: ({ getValue }) => (
194
+ <div className="text-gray-300 whitespace-nowrap">{getValue()}</div>
195
+ ),
188
196
  size: 80,
189
197
  sortingFn: sortSize,
190
198
  }),
191
199
  columnHelper.accessor('time', {
192
200
  header: 'Time',
193
- cell: ({ getValue }) => <div className="text-gray-300">{getValue()}</div>,
201
+ cell: ({ getValue }) => (
202
+ <div className="text-gray-300 whitespace-nowrap">{getValue()}</div>
203
+ ),
194
204
  size: 80,
195
205
  sortingFn: sortTime,
196
206
  }),
@@ -1,15 +1,42 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
+ import { cn } from '../utils/cn';
2
3
 
3
4
  export type SectionProps = {
4
5
  title: string;
5
6
  children: React.ReactNode;
7
+ collapsible?: boolean;
6
8
  };
7
9
 
8
- export const Section = ({ title, children }: SectionProps) => {
10
+ export const Section = ({
11
+ title,
12
+ children,
13
+ collapsible = true,
14
+ }: SectionProps) => {
15
+ const [isCollapsed, setIsCollapsed] = useState(false);
16
+
17
+ const isChildrenVisible = !collapsible || !isCollapsed;
18
+
19
+ const handleCollapseSection = () => {
20
+ setIsCollapsed((prevState) => !prevState);
21
+ };
22
+
23
+ const headerClassName = `flex items-center w-full text-left text-sm text-gray-300 mb-2 ${
24
+ collapsible ? 'hover:text-white' : 'cursor-default'
25
+ }`;
26
+
9
27
  return (
10
28
  <div>
11
- <h4 className="text-sm font-medium text-gray-300 mb-2">{title}</h4>
12
- {children}
29
+ <button
30
+ onClick={collapsible ? handleCollapseSection : undefined}
31
+ className={headerClassName}
32
+ tabIndex={collapsible ? 0 : -1}
33
+ >
34
+ {collapsible && (
35
+ <span className={cn('mr-2', { 'rotate-90': !isCollapsed })}>▶</span>
36
+ )}
37
+ <span className="font-medium">{title}</span>
38
+ </button>
39
+ {isChildrenVisible && children}
13
40
  </div>
14
41
  );
15
42
  };
@@ -1,4 +1,4 @@
1
- import { Initiator, ResourceType, RequestPostData } from '../../shared/client';
1
+ import { Initiator, ResourceType, HttpHeaders, RequestPostData } from '../../shared/client';
2
2
 
3
3
  export type RequestId = string;
4
4
  export type Timestamp = number;
@@ -21,7 +21,7 @@ export type HttpResponseData = {
21
21
  export type HttpRequest = {
22
22
  url: string;
23
23
  method: HttpMethod;
24
- headers: Record<string, string>;
24
+ headers: HttpHeaders;
25
25
  body?: HttpRequestData;
26
26
  };
27
27
 
@@ -29,7 +29,7 @@ export type HttpResponse = {
29
29
  url: string;
30
30
  status: number;
31
31
  statusText: string;
32
- headers: Record<string, string>;
32
+ headers: HttpHeaders;
33
33
  contentType: string;
34
34
  size: number;
35
35
  responseTime: Timestamp;
@@ -129,7 +129,7 @@ export type ProcessedRequest = {
129
129
  status: HttpStatus | WebSocketStatus | SSEStatus;
130
130
  timestamp: Timestamp;
131
131
  duration?: number;
132
- size?: number;
132
+ size: number | null;
133
133
  method: HttpMethod | 'WS' | 'SSE';
134
134
  httpStatus?: number;
135
135
  };