@rozenite/network-activity-plugin 1.0.0 → 1.2.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 (53) hide show
  1. package/README.md +9 -0
  2. package/dist/App.html +1 -1
  3. package/dist/assets/{App-C6wCDVkW.js → App-o_iVtD-5.js} +50 -7
  4. package/dist/boot-recording.cjs +1092 -0
  5. package/dist/boot-recording.js +1091 -0
  6. package/dist/react-native.cjs +3 -0
  7. package/dist/react-native.d.ts +3 -0
  8. package/dist/react-native.js +5 -1
  9. package/dist/rozenite.json +1 -1
  10. package/dist/src/react-native/boot-recording.d.ts +41 -0
  11. package/dist/src/react-native/config.d.ts +7 -4
  12. package/dist/src/react-native/events-listener.d.ts +44 -0
  13. package/dist/src/react-native/http/http-inspector.d.ts +10 -0
  14. package/dist/src/react-native/http/http-utils.d.ts +15 -0
  15. package/dist/src/react-native/inspector.d.ts +7 -0
  16. package/dist/src/react-native/network-inspector.d.ts +16 -0
  17. package/dist/src/react-native/sse/sse-inspector.d.ts +4 -7
  18. package/dist/src/react-native/useHttpInspector.d.ts +3 -0
  19. package/dist/src/react-native/useSSEInspector.d.ts +3 -0
  20. package/dist/src/react-native/useWebSocketInspector.d.ts +3 -0
  21. package/dist/src/react-native/websocket/websocket-inspector.d.ts +4 -7
  22. package/dist/src/shared/client.d.ts +3 -98
  23. package/dist/src/shared/http-events.d.ts +106 -0
  24. package/dist/src/shared/sse-events.d.ts +1 -1
  25. package/dist/src/ui/state/hooks.d.ts +3 -3
  26. package/dist/src/ui/state/model.d.ts +10 -0
  27. package/dist/useNetworkActivityDevTools.cjs +112 -993
  28. package/dist/useNetworkActivityDevTools.js +110 -989
  29. package/package.json +4 -4
  30. package/react-native.ts +8 -0
  31. package/src/react-native/boot-recording.ts +90 -0
  32. package/src/react-native/config.ts +9 -4
  33. package/src/react-native/events-listener.ts +102 -0
  34. package/src/react-native/http/http-inspector.ts +174 -0
  35. package/src/react-native/http/http-utils.ts +217 -0
  36. package/src/react-native/inspector.ts +10 -0
  37. package/src/react-native/network-inspector.ts +78 -0
  38. package/src/react-native/sse/sse-inspector.ts +12 -10
  39. package/src/react-native/useHttpInspector.ts +59 -0
  40. package/src/react-native/useNetworkActivityDevTools.ts +60 -115
  41. package/src/react-native/useSSEInspector.ts +35 -0
  42. package/src/react-native/useWebSocketInspector.ts +35 -0
  43. package/src/react-native/websocket/websocket-inspector.ts +18 -10
  44. package/src/shared/client.ts +4 -132
  45. package/src/shared/http-events.ts +140 -0
  46. package/src/shared/sse-events.ts +1 -1
  47. package/src/ui/components/RequestList.tsx +18 -6
  48. package/src/ui/components/Toolbar.tsx +3 -2
  49. package/src/ui/state/derived.ts +9 -3
  50. package/src/ui/state/model.ts +10 -0
  51. package/src/ui/state/store.ts +34 -3
  52. package/dist/src/react-native/http/network-inspector.d.ts +0 -8
  53. package/src/react-native/http/network-inspector.ts +0 -388
@@ -0,0 +1,140 @@
1
+ export type HttpHeaders = Record<string, string | string[]>;
2
+ export type XHRHeaders = NonNullable<XMLHttpRequest['responseHeaders']>;
3
+
4
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
5
+
6
+ export type RequestId = string;
7
+ export type Timestamp = number;
8
+
9
+ export type XHRPostData =
10
+ | string
11
+ | Blob
12
+ | FormData
13
+ | ArrayBuffer
14
+ | ArrayBufferView
15
+ | unknown
16
+ | null
17
+ | undefined;
18
+
19
+ export type RequestTextPostData = {
20
+ type: 'text';
21
+ value: string;
22
+ };
23
+
24
+ export type RequestBinaryPostData = {
25
+ type: 'binary';
26
+ value: {
27
+ size: number;
28
+ type?: string;
29
+ name?: string;
30
+ };
31
+ };
32
+
33
+ export type RequestFormDataPostData = {
34
+ type: 'form-data';
35
+ value: Record<string, RequestTextPostData | RequestBinaryPostData>;
36
+ };
37
+
38
+ export type RequestPostData =
39
+ | RequestTextPostData
40
+ | RequestFormDataPostData
41
+ | RequestBinaryPostData
42
+ | null
43
+ | undefined;
44
+
45
+ export type Cookie = {
46
+ name: string;
47
+ value: string;
48
+ domain?: string;
49
+ path?: string;
50
+ expires?: string;
51
+ maxAge?: string;
52
+ secure?: boolean;
53
+ httpOnly?: boolean;
54
+ sameSite?: string;
55
+ };
56
+
57
+ export type Request = {
58
+ url: string;
59
+ method: HttpMethod;
60
+ headers: HttpHeaders;
61
+ postData?: RequestPostData;
62
+ };
63
+
64
+ export type Response = {
65
+ url: string;
66
+ status: number;
67
+ statusText: string;
68
+ headers: HttpHeaders;
69
+ contentType: string;
70
+ size: number | null;
71
+ responseTime: Timestamp;
72
+ };
73
+
74
+ export type Initiator = {
75
+ type: string;
76
+ url?: string;
77
+ lineNumber?: number;
78
+ columnNumber?: number;
79
+ };
80
+
81
+ export type ResourceType = 'XHR' | 'Fetch' | 'Other';
82
+
83
+ export type RequestOverride = {
84
+ status?: number;
85
+ body?: string;
86
+ };
87
+
88
+ export type HttpEventMap = {
89
+ 'request-sent': {
90
+ requestId: RequestId;
91
+ request: Request;
92
+ timestamp: Timestamp;
93
+ initiator: Initiator;
94
+ type: ResourceType;
95
+ };
96
+
97
+ 'response-received': {
98
+ requestId: RequestId;
99
+ timestamp: Timestamp;
100
+ type: ResourceType;
101
+ response: Response;
102
+ };
103
+
104
+ 'request-completed': {
105
+ requestId: RequestId;
106
+ timestamp: Timestamp;
107
+ duration: number;
108
+ size: number | null;
109
+ ttfb: number;
110
+ };
111
+
112
+ 'request-failed': {
113
+ requestId: RequestId;
114
+ timestamp: Timestamp;
115
+ type: ResourceType;
116
+ error: string;
117
+ canceled: boolean;
118
+ };
119
+
120
+ 'request-progress': {
121
+ requestId: RequestId;
122
+ timestamp: Timestamp;
123
+ loaded: number;
124
+ total: number;
125
+ lengthComputable: boolean;
126
+ };
127
+
128
+ 'get-response-body': {
129
+ requestId: RequestId;
130
+ };
131
+
132
+ 'response-body': {
133
+ requestId: RequestId;
134
+ body: string | null;
135
+ };
136
+
137
+ 'set-overrides': {
138
+ overrides: [string, RequestOverride][];
139
+ };
140
+ };
@@ -1,4 +1,4 @@
1
- import type { Response } from './client';
1
+ import type { Response } from './http-events';
2
2
 
3
3
  export type SSEConnectionStatus = 'connecting' | 'open' | 'closed';
4
4
  export type SSERequestId = string;
@@ -61,7 +61,7 @@ const formatStartTime = (startTime: number): string => {
61
61
  };
62
62
 
63
63
  const extractDomainAndPath = (
64
- url: string
64
+ url: string,
65
65
  ): { domain: string; path: string } => {
66
66
  try {
67
67
  const { hostname, pathname, search, hash, port } = new URL(url);
@@ -80,7 +80,7 @@ const generateName = (url: string, showEntirePathName = false): string => {
80
80
  const urlObj = new URL(url);
81
81
  const pathname = urlObj.pathname;
82
82
  const filename = showEntirePathName ? undefined : pathname.split('/').pop();
83
-
83
+
84
84
  return filename || pathname || urlObj.hostname;
85
85
  } catch {
86
86
  return url;
@@ -128,17 +128,25 @@ const sortTime: SortingFn<NetworkRequest> = (rowA, rowB, columnId) => {
128
128
  const processNetworkRequests = (
129
129
  processedRequests: ProcessedRequest[],
130
130
  overrides: Map<string, RequestOverride>,
131
- showEntirePathAsName = false
131
+ showEntirePathAsName = false,
132
132
  ): NetworkRequest[] => {
133
133
  return processedRequests.map((request): NetworkRequest => {
134
134
  const { domain, path } = extractDomainAndPath(request.name);
135
135
  const duration = request.duration || 0;
136
136
  const hasOverride = overrides.has(request.name);
137
137
 
138
+ let statusDisplay: string | number = request.httpStatus || request.status;
139
+ if (request.status === 'loading' && request.progress?.lengthComputable) {
140
+ const percentage = Math.round(
141
+ (request.progress.loaded / request.progress.total) * 100,
142
+ );
143
+ statusDisplay = `${percentage}%`;
144
+ }
145
+
138
146
  return {
139
147
  id: request.id,
140
148
  name: generateName(request.name, showEntirePathAsName),
141
- status: request.httpStatus || request.status,
149
+ status: statusDisplay,
142
150
  method: request.method,
143
151
  domain,
144
152
  path,
@@ -254,7 +262,11 @@ export const RequestList = ({ filter }: RequestListProps) => {
254
262
  }, [processedRequests, filter]);
255
263
 
256
264
  const requests = useMemo(() => {
257
- return processNetworkRequests(filteredRequests, overrides, clientUISettings?.showUrlAsName);
265
+ return processNetworkRequests(
266
+ filteredRequests,
267
+ overrides,
268
+ clientUISettings?.showUrlAsName,
269
+ );
258
270
  }, [filteredRequests, overrides, clientUISettings?.showUrlAsName]);
259
271
 
260
272
  const table = useReactTable({
@@ -294,7 +306,7 @@ export const RequestList = ({ filter }: RequestListProps) => {
294
306
  ? null
295
307
  : flexRender(
296
308
  header.column.columnDef.header,
297
- header.getContext()
309
+ header.getContext(),
298
310
  )}
299
311
  {header.column.getCanSort() && (
300
312
  <span className="text-gray-500">
@@ -25,11 +25,12 @@ export const Toolbar = () => {
25
25
  ? 'text-red-400 hover:text-red-300'
26
26
  : 'text-gray-400 hover:text-blue-400'
27
27
  }`}
28
+ title={isRecording ? 'Stop recording' : 'Start recording'}
28
29
  >
29
30
  {isRecording ? (
30
- <Circle className="h-4 w-4 fill-current" />
31
+ <Square className="h-4 w-4 fill-current" />
31
32
  ) : (
32
- <Square className="h-4 w-4" />
33
+ <Circle className="h-4 w-4 fill-current" />
33
34
  )}
34
35
  </Button>
35
36
  <Button
@@ -21,9 +21,10 @@ export const getProcessedRequests = memoize((state: NetworkActivityState) => {
21
21
  status: httpEntry.status,
22
22
  timestamp: httpEntry.timestamp,
23
23
  duration: httpEntry.duration,
24
- size: httpEntry.size,
24
+ size: httpEntry.size ?? null,
25
25
  method: httpEntry.request.method,
26
26
  httpStatus: httpEntry.response?.status,
27
+ progress: httpEntry.progress,
27
28
  });
28
29
  } else if (entry.type === 'websocket') {
29
30
  const wsEntry = entry as WebSocketNetworkEntry;
@@ -34,6 +35,7 @@ export const getProcessedRequests = memoize((state: NetworkActivityState) => {
34
35
  status: wsEntry.status,
35
36
  timestamp: wsEntry.timestamp,
36
37
  duration: wsEntry.duration,
38
+ size: null,
37
39
  method: 'WS',
38
40
  httpStatus: 0,
39
41
  });
@@ -46,6 +48,7 @@ export const getProcessedRequests = memoize((state: NetworkActivityState) => {
46
48
  status: sseEntry.status,
47
49
  timestamp: sseEntry.timestamp,
48
50
  duration: sseEntry.duration,
51
+ size: null,
49
52
  method: 'SSE',
50
53
  httpStatus: 0,
51
54
  });
@@ -62,7 +65,7 @@ export const getSelectedRequest = memoize((state: NetworkActivityState) => {
62
65
  });
63
66
 
64
67
  export const getRequestSummary = (
65
- requestId: string
68
+ requestId: string,
66
69
  ): ((state: NetworkActivityState) => ProcessedRequest | null) =>
67
70
  memoize((state: NetworkActivityState) => {
68
71
  const { networkEntries } = state;
@@ -78,9 +81,10 @@ export const getRequestSummary = (
78
81
  status: httpEntry.status,
79
82
  timestamp: httpEntry.timestamp,
80
83
  duration: httpEntry.duration,
81
- size: httpEntry.size,
84
+ size: httpEntry.size ?? null,
82
85
  method: httpEntry.request.method,
83
86
  httpStatus: httpEntry.response?.status || 0,
87
+ progress: httpEntry.progress,
84
88
  };
85
89
  } else if (entry.type === 'websocket') {
86
90
  const wsEntry = entry as WebSocketNetworkEntry;
@@ -91,6 +95,7 @@ export const getRequestSummary = (
91
95
  status: wsEntry.status,
92
96
  timestamp: wsEntry.timestamp,
93
97
  duration: wsEntry.duration,
98
+ size: null,
94
99
  method: 'WS',
95
100
  httpStatus: 0,
96
101
  };
@@ -103,6 +108,7 @@ export const getRequestSummary = (
103
108
  status: sseEntry.status,
104
109
  timestamp: sseEntry.timestamp,
105
110
  duration: sseEntry.duration,
111
+ size: null,
106
112
  method: 'SSE',
107
113
  httpStatus: 0,
108
114
  };
@@ -58,6 +58,11 @@ export type HttpNetworkEntry = {
58
58
  size?: number;
59
59
  initiator?: Initiator;
60
60
  resourceType?: ResourceType;
61
+ progress?: {
62
+ loaded: number;
63
+ total: number;
64
+ lengthComputable: boolean;
65
+ };
61
66
  };
62
67
 
63
68
  /* SSE */
@@ -137,4 +142,9 @@ export type ProcessedRequest = {
137
142
  size: number | null;
138
143
  method: HttpMethod | 'WS' | 'SSE';
139
144
  httpStatus?: number;
145
+ progress?: {
146
+ loaded: number;
147
+ total: number;
148
+ lengthComputable: boolean;
149
+ };
140
150
  };
@@ -65,7 +65,7 @@ export const createNetworkActivityStore = () =>
65
65
  persist(
66
66
  (set, get) => ({
67
67
  // Initial state
68
- isRecording: false,
68
+ isRecording: true,
69
69
  selectedRequestId: null,
70
70
  networkEntries: new Map(),
71
71
  websocketMessages: new Map(),
@@ -168,6 +168,33 @@ export const createNetworkActivityStore = () =>
168
168
  break;
169
169
  }
170
170
 
171
+ case 'request-progress': {
172
+ const eventData =
173
+ data as NetworkActivityEventMap['request-progress'];
174
+ set((state) => {
175
+ const entry = state.networkEntries.get(eventData.requestId);
176
+ if (!entry || entry.type !== 'http') {
177
+ return state;
178
+ }
179
+
180
+ const httpEntry = entry as HttpNetworkEntry;
181
+ const updatedEntry: HttpNetworkEntry = {
182
+ ...httpEntry,
183
+ status: 'loading',
184
+ progress: {
185
+ loaded: eventData.loaded,
186
+ total: eventData.total,
187
+ lengthComputable: eventData.lengthComputable,
188
+ },
189
+ };
190
+
191
+ const newEntries = new Map(state.networkEntries);
192
+ newEntries.set(eventData.requestId, updatedEntry);
193
+ return { networkEntries: newEntries };
194
+ });
195
+ break;
196
+ }
197
+
171
198
  case 'response-received': {
172
199
  const eventData =
173
200
  data as NetworkActivityEventMap['response-received'];
@@ -559,6 +586,9 @@ export const createNetworkActivityStore = () =>
559
586
  client.onMessage('request-sent', (data) =>
560
587
  handleEvent('request-sent', data)
561
588
  ),
589
+ client.onMessage('request-progress', (data) =>
590
+ handleEvent('request-progress', data)
591
+ ),
562
592
  client.onMessage('response-received', (data) =>
563
593
  handleEvent('response-received', data)
564
594
  ),
@@ -654,9 +684,10 @@ export const createNetworkActivityStore = () =>
654
684
  typeof value === 'object' &&
655
685
  value !== null &&
656
686
  '_type' in value &&
657
- value._type === 'map'
687
+ value._type === 'map' &&
688
+ 'value' in value
658
689
  ) {
659
- return new Map(value.value);
690
+ return new Map(value.value as [string, RequestOverride][]);
660
691
  }
661
692
  return value;
662
693
  },
@@ -1,8 +0,0 @@
1
- import { NetworkActivityDevToolsClient } from '../../shared/client';
2
- export type NetworkInspector = {
3
- enable: () => void;
4
- disable: () => void;
5
- isEnabled: () => boolean;
6
- dispose: () => void;
7
- };
8
- export declare const getNetworkInspector: (pluginClient: NetworkActivityDevToolsClient) => NetworkInspector;