@rozenite/network-activity-plugin 1.0.0-alpha.9 → 1.1.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 (113) hide show
  1. package/README.md +2 -0
  2. package/dist/App.html +2 -2
  3. package/dist/assets/{App-DoHQsY5s.css → App-BrSkOkws.css} +223 -2
  4. package/dist/assets/{App-CA1Fbh0I.js → App-Kyi7zHUX.js} +8188 -2671
  5. package/dist/react-native.cjs +4 -1
  6. package/dist/react-native.js +4 -1
  7. package/dist/rozenite.json +1 -1
  8. package/dist/src/react-native/config.d.ts +20 -0
  9. package/dist/src/react-native/http/overrides-registry.d.ts +6 -0
  10. package/dist/src/react-native/http/xhr-interceptor.d.ts +7 -1
  11. package/dist/src/react-native/sse/sse-interceptor.d.ts +2 -2
  12. package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -1
  13. package/dist/src/react-native/utils/getBlobName.d.ts +35 -0
  14. package/dist/src/react-native/utils/getFormDataEntries.d.ts +18 -0
  15. package/dist/src/shared/client.d.ts +55 -4
  16. package/dist/src/shared/sse-events.d.ts +4 -1
  17. package/dist/src/ui/components/Button.d.ts +2 -2
  18. package/dist/src/ui/components/CodeBlock.d.ts +3 -0
  19. package/dist/src/ui/components/CodeEditor.d.ts +5 -0
  20. package/dist/src/ui/components/CookieCard.d.ts +7 -0
  21. package/dist/src/ui/components/CopyRequestDropdown.d.ts +7 -0
  22. package/dist/src/ui/components/DropdownMenu.d.ts +27 -0
  23. package/dist/src/ui/components/FilterBar.d.ts +10 -0
  24. package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +1 -1
  25. package/dist/src/ui/components/KeyValueGrid.d.ts +13 -0
  26. package/dist/src/ui/components/OverrideResponse.d.ts +8 -0
  27. package/dist/src/ui/components/RequestBody.d.ts +6 -0
  28. package/dist/src/ui/components/RequestList.d.ts +9 -4
  29. package/dist/src/ui/components/ScrollArea.d.ts +3 -2
  30. package/dist/src/ui/components/Section.d.ts +8 -0
  31. package/dist/src/ui/components/Separator.d.ts +2 -1
  32. package/dist/src/ui/components/Tabs.d.ts +7 -0
  33. package/dist/src/ui/state/hooks.d.ts +4 -0
  34. package/dist/src/ui/state/model.d.ts +22 -7
  35. package/dist/src/ui/state/store.d.ts +27 -3
  36. package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +2 -0
  37. package/dist/src/ui/utils/escapeShellArg.d.ts +1 -0
  38. package/dist/src/ui/utils/generateCurlCommand.d.ts +2 -0
  39. package/dist/src/ui/utils/generateFetchCall.d.ts +2 -0
  40. package/dist/src/ui/utils/generateMultipartBody.d.ts +4 -0
  41. package/dist/src/utils/applyReactNativeRequestHeadersLogic.d.ts +7 -0
  42. package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +9 -0
  43. package/dist/src/utils/cookieParser.d.ts +6 -0
  44. package/dist/src/utils/getContentTypeMimeType.d.ts +2 -0
  45. package/dist/src/utils/getHttpHeader.d.ts +5 -0
  46. package/dist/src/utils/getHttpHeaderValueAsString.d.ts +11 -0
  47. package/dist/src/utils/getStringSizeInBytes.d.ts +1 -0
  48. package/dist/src/utils/inferContentTypeFromPostData.d.ts +2 -0
  49. package/dist/src/utils/safeStringify.d.ts +1 -0
  50. package/dist/src/utils/typeChecks.d.ts +9 -0
  51. package/dist/useNetworkActivityDevTools.cjs +337 -24
  52. package/dist/useNetworkActivityDevTools.js +338 -25
  53. package/package.json +7 -4
  54. package/react-native.ts +6 -1
  55. package/src/react-native/config.ts +43 -0
  56. package/src/react-native/http/network-inspector.ts +190 -8
  57. package/src/react-native/http/overrides-registry.ts +32 -0
  58. package/src/react-native/http/xhr-interceptor.ts +19 -2
  59. package/src/react-native/sse/sse-inspector.ts +27 -5
  60. package/src/react-native/sse/sse-interceptor.ts +26 -8
  61. package/src/react-native/useNetworkActivityDevTools.ts +86 -8
  62. package/src/react-native/utils/getBlobName.ts +45 -0
  63. package/src/react-native/utils/getFormDataEntries.ts +32 -0
  64. package/src/react-native/utils.ts +3 -3
  65. package/src/shared/client.ts +81 -4
  66. package/src/shared/sse-events.ts +4 -1
  67. package/src/ui/components/Button.tsx +1 -0
  68. package/src/ui/components/CodeBlock.tsx +19 -0
  69. package/src/ui/components/CodeEditor.tsx +26 -0
  70. package/src/ui/components/CookieCard.tsx +64 -0
  71. package/src/ui/components/CopyRequestDropdown.tsx +95 -0
  72. package/src/ui/components/DropdownMenu.tsx +206 -0
  73. package/src/ui/components/FilterBar.tsx +117 -0
  74. package/src/ui/components/Input.tsx +1 -1
  75. package/src/ui/components/JsonTree.tsx +10 -3
  76. package/src/ui/components/JsonTreeCopyableItem.tsx +14 -10
  77. package/src/ui/components/KeyValueGrid.tsx +51 -0
  78. package/src/ui/components/OverrideResponse.tsx +132 -0
  79. package/src/ui/components/RequestBody.tsx +86 -0
  80. package/src/ui/components/RequestList.tsx +74 -14
  81. package/src/ui/components/ScrollArea.tsx +1 -0
  82. package/src/ui/components/Section.tsx +46 -0
  83. package/src/ui/components/SidePanel.tsx +15 -5
  84. package/src/ui/components/Toolbar.tsx +3 -2
  85. package/src/ui/globals.css +4 -0
  86. package/src/ui/hooks/useCopyToClipboard.ts +2 -2
  87. package/src/ui/state/derived.ts +2 -0
  88. package/src/ui/state/hooks.ts +8 -0
  89. package/src/ui/state/model.ts +28 -7
  90. package/src/ui/state/store.ts +640 -500
  91. package/src/ui/tabs/CookiesTab.tsx +60 -263
  92. package/src/ui/tabs/HeadersTab.tsx +78 -89
  93. package/src/ui/tabs/RequestTab.tsx +58 -46
  94. package/src/ui/tabs/ResponseTab.tsx +98 -67
  95. package/src/ui/tabs/SSEMessagesTab.tsx +50 -39
  96. package/src/ui/utils/checkRequestBodyBinary.ts +7 -0
  97. package/src/ui/utils/escapeShellArg.ts +12 -0
  98. package/src/ui/utils/generateCurlCommand.ts +83 -0
  99. package/src/ui/utils/generateFetchCall.ts +64 -0
  100. package/src/ui/utils/generateMultipartBody.ts +19 -0
  101. package/src/ui/views/InspectorView.tsx +15 -3
  102. package/src/utils/applyReactNativeRequestHeadersLogic.ts +30 -0
  103. package/src/utils/applyReactNativeResponseHeadersLogic.ts +28 -0
  104. package/src/utils/cookieParser.ts +126 -0
  105. package/src/utils/getContentTypeMimeType.ts +17 -0
  106. package/src/utils/getHttpHeader.ts +17 -0
  107. package/src/utils/getHttpHeaderValueAsString.ts +13 -0
  108. package/src/utils/getStringSizeInBytes.ts +3 -0
  109. package/src/utils/inferContentTypeFromPostData.ts +9 -0
  110. package/src/utils/safeStringify.ts +7 -0
  111. package/src/utils/typeChecks.ts +27 -0
  112. package/dist/src/ui/utils/getHttpHeaderValue.d.ts +0 -2
  113. package/src/ui/utils/getHttpHeaderValue.ts +0 -14
@@ -1,8 +1,11 @@
1
1
  import { createStore } from 'zustand';
2
+ import { persist, createJSONStorage } from 'zustand/middleware';
2
3
  import {
3
4
  NetworkActivityDevToolsClient,
4
5
  NetworkActivityEventMap,
6
+ RequestOverride,
5
7
  RequestId,
8
+ NetworkActivityClientUISettings,
6
9
  } from '../../shared/client';
7
10
  import {
8
11
  NetworkEntry,
@@ -12,19 +15,24 @@ import {
12
15
  SSENetworkEntry,
13
16
  SSEMessage,
14
17
  } from './model';
15
- import { getHttpHeaderValue } from '../utils/getHttpHeaderValue';
16
18
  import { getId } from '../utils/getId';
17
19
  import { assert } from '../utils/assert';
20
+ import { getContentTypeMime } from '../../utils/getContentTypeMimeType';
21
+ import { applyReactNativeRequestHeadersLogic } from '../../utils/applyReactNativeRequestHeadersLogic';
18
22
 
19
23
  const MAX_WEBSOCKET_MESSAGES_PER_CONNECTION = 32;
20
24
  const MAX_SSE_MESSAGES_PER_CONNECTION = 32;
21
25
 
26
+ const STORE_VERSION = 1;
27
+
22
28
  export interface NetworkActivityState {
23
29
  // State
24
30
  isRecording: boolean;
25
31
  selectedRequestId: RequestId | null;
26
32
  networkEntries: Map<RequestId, NetworkEntry>;
27
33
  websocketMessages: Map<RequestId, WebSocketMessage[]>;
34
+ overrides: Map<string, RequestOverride>;
35
+ clientUISettings: NetworkActivityClientUISettings | null;
28
36
 
29
37
  // Internal state (not exposed in interface)
30
38
  _unsubscribeFunctions?: Array<{ remove: () => void }>;
@@ -35,6 +43,8 @@ export interface NetworkActivityState {
35
43
  setRecording: (isRecording: boolean) => void;
36
44
  setSelectedRequest: (requestId: RequestId | null) => void;
37
45
  clearRequests: () => void;
46
+ addOverride: (requestUrl: string, override: RequestOverride) => void;
47
+ clearOverride: (requestUrl: string) => void;
38
48
  };
39
49
 
40
50
  // Event handling
@@ -51,509 +61,639 @@ export interface NetworkActivityState {
51
61
  }
52
62
 
53
63
  export const createNetworkActivityStore = () =>
54
- createStore<NetworkActivityState>((set, get) => ({
55
- // Initial state
56
- isRecording: false,
57
- selectedRequestId: null,
58
- networkEntries: new Map(),
59
- websocketMessages: new Map(),
60
-
61
- // Actions
62
- actions: {
63
- setRecording: (isRecording: boolean) => {
64
- const { _client } = get();
65
- assert(!!_client, 'Client is not set');
66
-
67
- _client.send(isRecording ? 'network-enable' : 'network-disable', {});
68
- set({ isRecording });
69
- },
70
- setSelectedRequest: (requestId: RequestId | null) =>
71
- set({ selectedRequestId: requestId }),
72
- clearRequests: () =>
73
- set({
74
- networkEntries: new Map(),
75
- websocketMessages: new Map(),
76
- selectedRequestId: null,
77
- }),
78
- },
79
- // Event handling
80
- handleEvent: <K extends keyof NetworkActivityEventMap>(
81
- eventType: K,
82
- data: NetworkActivityEventMap[K]
83
- ) => {
84
- switch (eventType) {
85
- case 'request-sent': {
86
- const eventData = data as NetworkActivityEventMap['request-sent'];
87
- set((state) => {
88
- const entry: HttpNetworkEntry = {
89
- id: eventData.requestId,
90
- type: 'http',
91
- timestamp: eventData.timestamp,
92
- request: {
93
- url: eventData.request.url,
94
- method: eventData.request.method,
95
- headers: eventData.request.headers,
96
- body: eventData.request.postData
97
- ? {
98
- type:
99
- getHttpHeaderValue(
100
- eventData.request.headers,
101
- 'content-type'
102
- )?.split(';')[0] || 'text/plain',
103
- data: eventData.request.postData,
104
- }
105
- : undefined,
106
- },
107
- status: 'pending',
108
- initiator: eventData.initiator,
109
- resourceType: eventData.type,
110
- };
111
-
112
- const newEntries = new Map(state.networkEntries);
113
- newEntries.set(eventData.requestId, entry);
114
- return { networkEntries: newEntries };
115
- });
116
- break;
117
- }
118
-
119
- case 'response-received': {
120
- const eventData =
121
- data as NetworkActivityEventMap['response-received'];
122
- set((state) => {
123
- const entry = state.networkEntries.get(eventData.requestId);
124
- if (!entry || entry.type !== 'http') return state;
125
-
126
- const httpEntry = entry as HttpNetworkEntry;
127
- const updatedEntry: HttpNetworkEntry = {
128
- ...httpEntry,
129
- status: 'loading',
130
- response: eventData.response,
131
- };
132
-
133
- const newEntries = new Map(state.networkEntries);
134
- newEntries.set(eventData.requestId, updatedEntry);
135
- return { networkEntries: newEntries };
136
- });
137
- break;
138
- }
139
-
140
- case 'request-completed': {
141
- const eventData =
142
- data as NetworkActivityEventMap['request-completed'];
143
- set((state) => {
144
- const entry = state.networkEntries.get(eventData.requestId);
145
- if (!entry || entry.type !== 'http') return state;
146
-
147
- const httpEntry = entry as HttpNetworkEntry;
148
- const updatedEntry: HttpNetworkEntry = {
149
- ...httpEntry,
150
- status: 'finished',
151
- duration: eventData.duration,
152
- size: eventData.size,
153
- ttfb: eventData.ttfb,
154
- };
155
-
156
- const newEntries = new Map(state.networkEntries);
157
- newEntries.set(eventData.requestId, updatedEntry);
158
- return { networkEntries: newEntries };
159
- });
160
- break;
161
- }
162
-
163
- case 'request-failed': {
164
- const eventData = data as NetworkActivityEventMap['request-failed'];
165
- set((state) => {
166
- const entry = state.networkEntries.get(eventData.requestId);
167
- if (!entry || entry.type !== 'http') return state;
168
-
169
- const httpEntry = entry as HttpNetworkEntry;
170
- const updatedEntry: HttpNetworkEntry = {
171
- ...httpEntry,
172
- status: 'failed',
173
- error: eventData.error,
174
- };
175
-
176
- const newEntries = new Map(state.networkEntries);
177
- newEntries.set(eventData.requestId, updatedEntry);
178
- return { networkEntries: newEntries };
179
- });
180
- break;
181
- }
182
-
183
- case 'response-body': {
184
- const eventData = data as NetworkActivityEventMap['response-body'];
185
- set((state) => {
186
- const entry = state.networkEntries.get(eventData.requestId);
187
- if (!entry || entry.type !== 'http') return state;
188
-
189
- const httpEntry = entry as HttpNetworkEntry;
190
- const updatedEntry: HttpNetworkEntry = {
191
- ...httpEntry,
192
- response: httpEntry.response
193
- ? {
194
- ...httpEntry.response,
195
- body: eventData.body
64
+ createStore<NetworkActivityState>()(
65
+ persist(
66
+ (set, get) => ({
67
+ // Initial state
68
+ isRecording: false,
69
+ selectedRequestId: null,
70
+ networkEntries: new Map(),
71
+ websocketMessages: new Map(),
72
+ overrides: new Map(),
73
+ clientUISettings: null,
74
+
75
+ // Actions
76
+ actions: {
77
+ setRecording: (isRecording: boolean) => {
78
+ const { _client } = get();
79
+ assert(!!_client, 'Client is not set');
80
+
81
+ _client.send(
82
+ isRecording ? 'network-enable' : 'network-disable',
83
+ {}
84
+ );
85
+ set({ isRecording });
86
+ },
87
+ setSelectedRequest: (requestId: RequestId | null) =>
88
+ set({ selectedRequestId: requestId }),
89
+ clearRequests: () =>
90
+ set({
91
+ networkEntries: new Map(),
92
+ websocketMessages: new Map(),
93
+ selectedRequestId: null,
94
+ }),
95
+ addOverride: (requestUrl: string, override: RequestOverride) => {
96
+ const { overrides, _client } = get();
97
+ assert(!!_client, 'Client is not set');
98
+
99
+ const newOverrides = new Map(overrides);
100
+ newOverrides.set(requestUrl, override);
101
+
102
+ _client.send('set-overrides', {
103
+ overrides: Array.from(newOverrides.entries()),
104
+ });
105
+ set({ overrides: newOverrides });
106
+ },
107
+ clearOverride: (requestUrl: string) => {
108
+ const { overrides, _client } = get();
109
+ assert(!!_client, 'Client is not set');
110
+
111
+ const newOverrides = new Map(overrides);
112
+ newOverrides.delete(requestUrl);
113
+
114
+ _client.send('set-overrides', {
115
+ overrides: Array.from(newOverrides.entries()),
116
+ });
117
+ set({ overrides: newOverrides });
118
+ },
119
+ },
120
+ // Event handling
121
+ handleEvent: <K extends keyof NetworkActivityEventMap>(
122
+ eventType: K,
123
+ data: NetworkActivityEventMap[K]
124
+ ) => {
125
+ switch (eventType) {
126
+ case 'client-ui-settings': {
127
+ const eventData = data as NetworkActivityEventMap['client-ui-settings'];
128
+ set({ clientUISettings: eventData.settings || null });
129
+ break;
130
+ }
131
+
132
+ case 'request-sent': {
133
+ const eventData = data as NetworkActivityEventMap['request-sent'];
134
+ set((state) => {
135
+ const headersWithContentType =
136
+ applyReactNativeRequestHeadersLogic(
137
+ eventData.request.headers,
138
+ eventData.request.postData
139
+ );
140
+
141
+ const requestContentType =
142
+ getContentTypeMime(headersWithContentType) || 'text/plain';
143
+
144
+ const entry: HttpNetworkEntry = {
145
+ id: eventData.requestId,
146
+ type: 'http',
147
+ timestamp: eventData.timestamp,
148
+ request: {
149
+ url: eventData.request.url,
150
+ method: eventData.request.method,
151
+ headers: headersWithContentType,
152
+ body: eventData.request.postData
196
153
  ? {
197
- type:
198
- getHttpHeaderValue(
199
- httpEntry.response?.headers ?? {},
200
- 'content-type'
201
- )?.split(';')[0] || 'text/plain',
202
- data: eventData.body,
154
+ type: requestContentType,
155
+ data: eventData.request.postData,
203
156
  }
204
157
  : undefined,
205
- }
206
- : undefined,
207
- };
208
-
209
- const newEntries = new Map(state.networkEntries);
210
- newEntries.set(eventData.requestId, updatedEntry);
211
- return { networkEntries: newEntries };
212
- });
213
- break;
214
- }
215
-
216
- case 'websocket-connect': {
217
- const eventData =
218
- data as NetworkActivityEventMap['websocket-connect'];
219
- set((state) => {
220
- const entry: WebSocketNetworkEntry = {
221
- id: `ws-${eventData.socketId}`,
222
- type: 'websocket',
223
- timestamp: eventData.timestamp,
224
- connection: {
225
- url: eventData.url,
226
- socketId: eventData.socketId,
227
- protocols: eventData.protocols || undefined,
228
- options: eventData.options,
229
- },
230
- status: 'connecting',
231
- };
232
-
233
- const newEntries = new Map(state.networkEntries);
234
- newEntries.set(entry.id, entry);
235
-
236
- const newMessages = new Map(state.websocketMessages);
237
- newMessages.set(entry.id, []);
238
-
239
- return {
240
- networkEntries: newEntries,
241
- websocketMessages: newMessages,
242
- };
243
- });
244
- break;
245
- }
246
-
247
- case 'websocket-open': {
248
- const eventData = data as NetworkActivityEventMap['websocket-open'];
249
- set((state) => {
250
- const entry = state.networkEntries.get(`ws-${eventData.socketId}`);
251
- if (!entry || entry.type !== 'websocket') return state;
252
-
253
- const wsEntry = entry as WebSocketNetworkEntry;
254
- const updatedEntry: WebSocketNetworkEntry = {
255
- ...wsEntry,
256
- status: 'open',
257
- };
258
-
259
- const newEntries = new Map(state.networkEntries);
260
- newEntries.set(entry.id, updatedEntry);
261
- return { networkEntries: newEntries };
262
- });
263
- break;
264
- }
265
-
266
- case 'websocket-close': {
267
- const eventData = data as NetworkActivityEventMap['websocket-close'];
268
- set((state) => {
269
- const entry = state.networkEntries.get(`ws-${eventData.socketId}`);
270
- if (!entry || entry.type !== 'websocket') return state;
271
-
272
- const wsEntry = entry as WebSocketNetworkEntry;
273
- const updatedEntry: WebSocketNetworkEntry = {
274
- ...wsEntry,
275
- status: 'closed',
276
- closeCode: eventData.code,
277
- closeReason: eventData.reason,
278
- duration: eventData.timestamp - wsEntry.timestamp,
279
- };
280
-
281
- const newEntries = new Map(state.networkEntries);
282
- newEntries.set(entry.id, updatedEntry);
283
- return { networkEntries: newEntries };
284
- });
285
- break;
286
- }
287
-
288
- case 'websocket-message-sent': {
289
- const eventData =
290
- data as NetworkActivityEventMap['websocket-message-sent'];
291
- set((state) => {
292
- const socketId = `ws-${eventData.socketId}`;
293
- const currentMessages = state.websocketMessages.get(socketId) || [];
294
-
295
- const message: WebSocketMessage = {
296
- id: getId(`${socketId}-message`),
297
- direction: 'sent',
298
- data: eventData.data,
299
- messageType: eventData.messageType,
300
- timestamp: eventData.timestamp,
301
- };
302
-
303
- const newMessages = new Map(state.websocketMessages);
304
- newMessages.set(
305
- socketId,
306
- [...currentMessages, message].slice(
307
- -MAX_WEBSOCKET_MESSAGES_PER_CONNECTION
308
- )
309
- );
310
-
311
- return { websocketMessages: newMessages };
312
- });
313
- break;
314
- }
315
-
316
- case 'websocket-message-received': {
317
- const eventData =
318
- data as NetworkActivityEventMap['websocket-message-received'];
319
- set((state) => {
320
- const socketId = `ws-${eventData.socketId}`;
321
- const currentMessages = state.websocketMessages.get(socketId) || [];
322
-
323
- const message: WebSocketMessage = {
324
- id: getId(`${socketId}-message`),
325
- direction: 'received',
326
- data: eventData.data,
327
- messageType: eventData.messageType,
328
- timestamp: eventData.timestamp,
329
- };
330
-
331
- const newMessages = new Map(state.websocketMessages);
332
- newMessages.set(
333
- socketId,
334
- [...currentMessages, message].slice(
335
- -MAX_WEBSOCKET_MESSAGES_PER_CONNECTION
336
- )
337
- );
338
-
339
- return { websocketMessages: newMessages };
340
- });
341
- break;
342
- }
343
-
344
- case 'websocket-error': {
345
- const eventData = data as NetworkActivityEventMap['websocket-error'];
346
- set((state) => {
347
- const entry = state.networkEntries.get(`ws-${eventData.socketId}`);
348
- if (!entry || entry.type !== 'websocket') return state;
349
-
350
- const wsEntry = entry as WebSocketNetworkEntry;
351
- const updatedEntry: WebSocketNetworkEntry = {
352
- ...wsEntry,
353
- status: 'error',
354
- error: eventData.error,
355
- };
356
-
357
- const newEntries = new Map(state.networkEntries);
358
- newEntries.set(entry.id, updatedEntry);
359
- return { networkEntries: newEntries };
360
- });
361
- break;
362
- }
363
-
364
- case 'websocket-connection-status-changed': {
365
- const eventData =
366
- data as NetworkActivityEventMap['websocket-connection-status-changed'];
367
- set((state) => {
368
- const entry = state.networkEntries.get(`ws-${eventData.socketId}`);
369
- if (!entry || entry.type !== 'websocket') return state;
370
-
371
- const wsEntry = entry as WebSocketNetworkEntry;
372
- const updatedEntry: WebSocketNetworkEntry = {
373
- ...wsEntry,
374
- status: eventData.status,
375
- };
376
-
377
- const newEntries = new Map(state.networkEntries);
378
- newEntries.set(entry.id, updatedEntry);
379
- return { networkEntries: newEntries };
380
- });
381
- break;
382
- }
383
-
384
- case 'sse-open': {
385
- const eventData = data as NetworkActivityEventMap['sse-open'];
386
- set((state) => {
387
- const entry = state.networkEntries.get(eventData.requestId);
388
- if (!entry || entry.type !== 'http') return state;
389
-
390
- // Transform the existing HTTP entry to SSE
391
- const httpEntry = entry as HttpNetworkEntry;
392
- const sseEntry: SSENetworkEntry = {
393
- ...httpEntry,
394
- type: 'sse', // Change type from 'http' to 'sse'
395
- status: 'open', // Update status
396
- messages: [], // Add SSE-specific field
397
- response: eventData.response,
398
- };
399
-
400
- const newEntries = new Map(state.networkEntries);
401
- newEntries.set(eventData.requestId, sseEntry);
402
- return { networkEntries: newEntries };
403
- });
404
- break;
405
- }
406
-
407
- case 'sse-message': {
408
- const eventData = data as NetworkActivityEventMap['sse-message'];
409
- set((state) => {
410
- const entry = state.networkEntries.get(eventData.requestId);
411
- if (!entry || entry.type !== 'sse') return state;
412
-
413
- const sseEntry = entry as SSENetworkEntry;
414
- const newMessage: SSEMessage = {
415
- id: getId(`${eventData.requestId}-message`),
416
- data: eventData.data,
417
- timestamp: eventData.timestamp,
418
- };
419
-
420
- const updatedEntry: SSENetworkEntry = {
421
- ...sseEntry,
422
- messages: [...sseEntry.messages, newMessage].slice(
423
- -MAX_SSE_MESSAGES_PER_CONNECTION
158
+ },
159
+ status: 'pending',
160
+ initiator: eventData.initiator,
161
+ resourceType: eventData.type,
162
+ };
163
+
164
+ const newEntries = new Map(state.networkEntries);
165
+ newEntries.set(eventData.requestId, entry);
166
+ return { networkEntries: newEntries };
167
+ });
168
+ break;
169
+ }
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
+
198
+ case 'response-received': {
199
+ const eventData =
200
+ data as NetworkActivityEventMap['response-received'];
201
+ set((state) => {
202
+ const entry = state.networkEntries.get(eventData.requestId);
203
+ if (!entry || entry.type !== 'http') return state;
204
+
205
+ const httpEntry = entry as HttpNetworkEntry;
206
+ const updatedEntry: HttpNetworkEntry = {
207
+ ...httpEntry,
208
+ status: 'loading',
209
+ response: {
210
+ ...eventData.response,
211
+ size: eventData.response.size ?? 0,
212
+ },
213
+ };
214
+
215
+ const newEntries = new Map(state.networkEntries);
216
+ newEntries.set(eventData.requestId, updatedEntry);
217
+ return { networkEntries: newEntries };
218
+ });
219
+ break;
220
+ }
221
+
222
+ case 'request-completed': {
223
+ const eventData =
224
+ data as NetworkActivityEventMap['request-completed'];
225
+ set((state) => {
226
+ const entry = state.networkEntries.get(eventData.requestId);
227
+ if (!entry || entry.type !== 'http') return state;
228
+
229
+ const httpEntry = entry as HttpNetworkEntry;
230
+ const updatedEntry: HttpNetworkEntry = {
231
+ ...httpEntry,
232
+ status: 'finished',
233
+ duration: eventData.duration,
234
+ size: eventData.size ?? undefined,
235
+ ttfb: eventData.ttfb,
236
+ };
237
+
238
+ const newEntries = new Map(state.networkEntries);
239
+ newEntries.set(eventData.requestId, updatedEntry);
240
+ return { networkEntries: newEntries };
241
+ });
242
+ break;
243
+ }
244
+
245
+ case 'request-failed': {
246
+ const eventData =
247
+ data as NetworkActivityEventMap['request-failed'];
248
+ set((state) => {
249
+ const entry = state.networkEntries.get(eventData.requestId);
250
+ if (!entry || entry.type !== 'http') return state;
251
+
252
+ const httpEntry = entry as HttpNetworkEntry;
253
+ const updatedEntry: HttpNetworkEntry = {
254
+ ...httpEntry,
255
+ status: 'failed',
256
+ error: eventData.error,
257
+ };
258
+
259
+ const newEntries = new Map(state.networkEntries);
260
+ newEntries.set(eventData.requestId, updatedEntry);
261
+ return { networkEntries: newEntries };
262
+ });
263
+ break;
264
+ }
265
+
266
+ case 'response-body': {
267
+ const eventData =
268
+ data as NetworkActivityEventMap['response-body'];
269
+ set((state) => {
270
+ const entry = state.networkEntries.get(eventData.requestId);
271
+ if (!entry || entry.type !== 'http') return state;
272
+
273
+ const httpEntry = entry as HttpNetworkEntry;
274
+ const updatedEntry: HttpNetworkEntry = {
275
+ ...httpEntry,
276
+ response: httpEntry.response
277
+ ? {
278
+ ...httpEntry.response,
279
+ body: eventData.body
280
+ ? {
281
+ type:
282
+ getContentTypeMime(
283
+ httpEntry.response?.headers ?? {}
284
+ ) || 'text/plain',
285
+ data: eventData.body,
286
+ }
287
+ : undefined,
288
+ }
289
+ : undefined,
290
+ };
291
+
292
+ const newEntries = new Map(state.networkEntries);
293
+ newEntries.set(eventData.requestId, updatedEntry);
294
+ return { networkEntries: newEntries };
295
+ });
296
+ break;
297
+ }
298
+
299
+ case 'websocket-connect': {
300
+ const eventData =
301
+ data as NetworkActivityEventMap['websocket-connect'];
302
+ set((state) => {
303
+ const entry: WebSocketNetworkEntry = {
304
+ id: `ws-${eventData.socketId}`,
305
+ type: 'websocket',
306
+ timestamp: eventData.timestamp,
307
+ connection: {
308
+ url: eventData.url,
309
+ socketId: eventData.socketId,
310
+ protocols: eventData.protocols || undefined,
311
+ options: eventData.options,
312
+ },
313
+ status: 'connecting',
314
+ };
315
+
316
+ const newEntries = new Map(state.networkEntries);
317
+ newEntries.set(entry.id, entry);
318
+
319
+ const newMessages = new Map(state.websocketMessages);
320
+ newMessages.set(entry.id, []);
321
+
322
+ return {
323
+ networkEntries: newEntries,
324
+ websocketMessages: newMessages,
325
+ };
326
+ });
327
+ break;
328
+ }
329
+
330
+ case 'websocket-open': {
331
+ const eventData =
332
+ data as NetworkActivityEventMap['websocket-open'];
333
+ set((state) => {
334
+ const entry = state.networkEntries.get(
335
+ `ws-${eventData.socketId}`
336
+ );
337
+ if (!entry || entry.type !== 'websocket') return state;
338
+
339
+ const wsEntry = entry as WebSocketNetworkEntry;
340
+ const updatedEntry: WebSocketNetworkEntry = {
341
+ ...wsEntry,
342
+ status: 'open',
343
+ };
344
+
345
+ const newEntries = new Map(state.networkEntries);
346
+ newEntries.set(entry.id, updatedEntry);
347
+ return { networkEntries: newEntries };
348
+ });
349
+ break;
350
+ }
351
+
352
+ case 'websocket-close': {
353
+ const eventData =
354
+ data as NetworkActivityEventMap['websocket-close'];
355
+ set((state) => {
356
+ const entry = state.networkEntries.get(
357
+ `ws-${eventData.socketId}`
358
+ );
359
+ if (!entry || entry.type !== 'websocket') return state;
360
+
361
+ const wsEntry = entry as WebSocketNetworkEntry;
362
+ const updatedEntry: WebSocketNetworkEntry = {
363
+ ...wsEntry,
364
+ status: 'closed',
365
+ closeCode: eventData.code,
366
+ closeReason: eventData.reason,
367
+ duration: eventData.timestamp - wsEntry.timestamp,
368
+ };
369
+
370
+ const newEntries = new Map(state.networkEntries);
371
+ newEntries.set(entry.id, updatedEntry);
372
+ return { networkEntries: newEntries };
373
+ });
374
+ break;
375
+ }
376
+
377
+ case 'websocket-message-sent': {
378
+ const eventData =
379
+ data as NetworkActivityEventMap['websocket-message-sent'];
380
+ set((state) => {
381
+ const socketId = `ws-${eventData.socketId}`;
382
+ const currentMessages =
383
+ state.websocketMessages.get(socketId) || [];
384
+
385
+ const message: WebSocketMessage = {
386
+ id: getId(`${socketId}-message`),
387
+ direction: 'sent',
388
+ data: eventData.data,
389
+ messageType: eventData.messageType,
390
+ timestamp: eventData.timestamp,
391
+ };
392
+
393
+ const newMessages = new Map(state.websocketMessages);
394
+ newMessages.set(
395
+ socketId,
396
+ [...currentMessages, message].slice(
397
+ -MAX_WEBSOCKET_MESSAGES_PER_CONNECTION
398
+ )
399
+ );
400
+
401
+ return { websocketMessages: newMessages };
402
+ });
403
+ break;
404
+ }
405
+
406
+ case 'websocket-message-received': {
407
+ const eventData =
408
+ data as NetworkActivityEventMap['websocket-message-received'];
409
+ set((state) => {
410
+ const socketId = `ws-${eventData.socketId}`;
411
+ const currentMessages =
412
+ state.websocketMessages.get(socketId) || [];
413
+
414
+ const message: WebSocketMessage = {
415
+ id: getId(`${socketId}-message`),
416
+ direction: 'received',
417
+ data: eventData.data,
418
+ messageType: eventData.messageType,
419
+ timestamp: eventData.timestamp,
420
+ };
421
+
422
+ const newMessages = new Map(state.websocketMessages);
423
+ newMessages.set(
424
+ socketId,
425
+ [...currentMessages, message].slice(
426
+ -MAX_WEBSOCKET_MESSAGES_PER_CONNECTION
427
+ )
428
+ );
429
+
430
+ return { websocketMessages: newMessages };
431
+ });
432
+ break;
433
+ }
434
+
435
+ case 'websocket-error': {
436
+ const eventData =
437
+ data as NetworkActivityEventMap['websocket-error'];
438
+ set((state) => {
439
+ const entry = state.networkEntries.get(
440
+ `ws-${eventData.socketId}`
441
+ );
442
+ if (!entry || entry.type !== 'websocket') return state;
443
+
444
+ const wsEntry = entry as WebSocketNetworkEntry;
445
+ const updatedEntry: WebSocketNetworkEntry = {
446
+ ...wsEntry,
447
+ status: 'error',
448
+ error: eventData.error,
449
+ };
450
+
451
+ const newEntries = new Map(state.networkEntries);
452
+ newEntries.set(entry.id, updatedEntry);
453
+ return { networkEntries: newEntries };
454
+ });
455
+ break;
456
+ }
457
+
458
+ case 'websocket-connection-status-changed': {
459
+ const eventData =
460
+ data as NetworkActivityEventMap['websocket-connection-status-changed'];
461
+ set((state) => {
462
+ const entry = state.networkEntries.get(
463
+ `ws-${eventData.socketId}`
464
+ );
465
+ if (!entry || entry.type !== 'websocket') return state;
466
+
467
+ const wsEntry = entry as WebSocketNetworkEntry;
468
+ const updatedEntry: WebSocketNetworkEntry = {
469
+ ...wsEntry,
470
+ status: eventData.status,
471
+ };
472
+
473
+ const newEntries = new Map(state.networkEntries);
474
+ newEntries.set(entry.id, updatedEntry);
475
+ return { networkEntries: newEntries };
476
+ });
477
+ break;
478
+ }
479
+
480
+ case 'sse-open': {
481
+ const eventData = data as NetworkActivityEventMap['sse-open'];
482
+ set((state) => {
483
+ const entry = state.networkEntries.get(eventData.requestId);
484
+ if (!entry || entry.type !== 'http') return state;
485
+
486
+ // Transform the existing HTTP entry to SSE
487
+ const httpEntry = entry as HttpNetworkEntry;
488
+ const sseEntry: SSENetworkEntry = {
489
+ ...httpEntry,
490
+ type: 'sse', // Change type from 'http' to 'sse'
491
+ status: 'open', // Update status
492
+ messages: [], // Add SSE-specific field
493
+ response: {
494
+ ...eventData.response,
495
+ size: eventData.response.size ?? 0,
496
+ },
497
+ };
498
+
499
+ const newEntries = new Map(state.networkEntries);
500
+ newEntries.set(eventData.requestId, sseEntry);
501
+ return { networkEntries: newEntries };
502
+ });
503
+ break;
504
+ }
505
+
506
+ case 'sse-message': {
507
+ const eventData = data as NetworkActivityEventMap['sse-message'];
508
+ set((state) => {
509
+ const entry = state.networkEntries.get(eventData.requestId);
510
+ if (!entry || entry.type !== 'sse') return state;
511
+
512
+ const sseEntry = entry as SSENetworkEntry;
513
+ const newMessage: SSEMessage = {
514
+ id: getId(`${eventData.requestId}-message`),
515
+ type: eventData.payload.type,
516
+ data: eventData.payload.data,
517
+ timestamp: eventData.timestamp,
518
+ };
519
+
520
+ const updatedEntry: SSENetworkEntry = {
521
+ ...sseEntry,
522
+ messages: [...sseEntry.messages, newMessage].slice(
523
+ -MAX_SSE_MESSAGES_PER_CONNECTION
524
+ ),
525
+ };
526
+
527
+ const newEntries = new Map(state.networkEntries);
528
+ newEntries.set(eventData.requestId, updatedEntry);
529
+ return { networkEntries: newEntries };
530
+ });
531
+ break;
532
+ }
533
+
534
+ case 'sse-error': {
535
+ const eventData = data as NetworkActivityEventMap['sse-error'];
536
+ set((state) => {
537
+ const entry = state.networkEntries.get(eventData.requestId);
538
+ if (!entry || entry.type !== 'sse') return state;
539
+
540
+ const sseEntry = entry as SSENetworkEntry;
541
+ const updatedEntry: SSENetworkEntry = {
542
+ ...sseEntry,
543
+ status: 'error',
544
+ error: eventData.error.message,
545
+ };
546
+
547
+ const newEntries = new Map(state.networkEntries);
548
+ newEntries.set(eventData.requestId, updatedEntry);
549
+ return { networkEntries: newEntries };
550
+ });
551
+ break;
552
+ }
553
+
554
+ case 'sse-close': {
555
+ const eventData = data as NetworkActivityEventMap['sse-close'];
556
+ set((state) => {
557
+ const entry = state.networkEntries.get(eventData.requestId);
558
+ if (!entry || entry.type !== 'sse') return state;
559
+
560
+ const sseEntry = entry as SSENetworkEntry;
561
+ const updatedEntry: SSENetworkEntry = {
562
+ ...sseEntry,
563
+ status: 'closed',
564
+ duration: eventData.timestamp - sseEntry.timestamp,
565
+ };
566
+
567
+ const newEntries = new Map(state.networkEntries);
568
+ newEntries.set(eventData.requestId, updatedEntry);
569
+ return { networkEntries: newEntries };
570
+ });
571
+ break;
572
+ }
573
+ }
574
+ },
575
+
576
+ // Client management
577
+ client: {
578
+ setupClient: (client: NetworkActivityDevToolsClient) => {
579
+ const { handleEvent } = get();
580
+
581
+ // Subscribe to all events using the unified handler
582
+ const unsubscribeFunctions = [
583
+ client.onMessage('client-ui-settings', (data) =>
584
+ handleEvent('client-ui-settings', data)
585
+ ),
586
+ client.onMessage('request-sent', (data) =>
587
+ handleEvent('request-sent', data)
588
+ ),
589
+ client.onMessage('request-progress', (data) =>
590
+ handleEvent('request-progress', data)
591
+ ),
592
+ client.onMessage('response-received', (data) =>
593
+ handleEvent('response-received', data)
594
+ ),
595
+ client.onMessage('request-completed', (data) =>
596
+ handleEvent('request-completed', data)
597
+ ),
598
+ client.onMessage('request-failed', (data) =>
599
+ handleEvent('request-failed', data)
600
+ ),
601
+ client.onMessage('response-body', (data) =>
602
+ handleEvent('response-body', data)
424
603
  ),
425
- };
426
-
427
- const newEntries = new Map(state.networkEntries);
428
- newEntries.set(eventData.requestId, updatedEntry);
429
- return { networkEntries: newEntries };
430
- });
431
- break;
432
- }
433
-
434
- case 'sse-error': {
435
- const eventData = data as NetworkActivityEventMap['sse-error'];
436
- set((state) => {
437
- const entry = state.networkEntries.get(eventData.requestId);
438
- if (!entry || entry.type !== 'sse') return state;
439
-
440
- const sseEntry = entry as SSENetworkEntry;
441
- const updatedEntry: SSENetworkEntry = {
442
- ...sseEntry,
443
- status: 'error',
444
- error: eventData.error.message,
445
- };
446
-
447
- const newEntries = new Map(state.networkEntries);
448
- newEntries.set(eventData.requestId, updatedEntry);
449
- return { networkEntries: newEntries };
450
- });
451
- break;
452
- }
453
-
454
- case 'sse-close': {
455
- const eventData = data as NetworkActivityEventMap['sse-close'];
456
- set((state) => {
457
- const entry = state.networkEntries.get(eventData.requestId);
458
- if (!entry || entry.type !== 'sse') return state;
459
-
460
- const sseEntry = entry as SSENetworkEntry;
461
- const updatedEntry: SSENetworkEntry = {
462
- ...sseEntry,
463
- status: 'closed',
464
- duration: eventData.timestamp - sseEntry.timestamp,
465
- };
466
-
467
- const newEntries = new Map(state.networkEntries);
468
- newEntries.set(eventData.requestId, updatedEntry);
469
- return { networkEntries: newEntries };
470
- });
471
- break;
472
- }
604
+ client.onMessage('websocket-connect', (data) =>
605
+ handleEvent('websocket-connect', data)
606
+ ),
607
+ client.onMessage('websocket-open', (data) =>
608
+ handleEvent('websocket-open', data)
609
+ ),
610
+ client.onMessage('websocket-close', (data) =>
611
+ handleEvent('websocket-close', data)
612
+ ),
613
+ client.onMessage('websocket-message-sent', (data) =>
614
+ handleEvent('websocket-message-sent', data)
615
+ ),
616
+ client.onMessage('websocket-message-received', (data) =>
617
+ handleEvent('websocket-message-received', data)
618
+ ),
619
+ client.onMessage('websocket-error', (data) =>
620
+ handleEvent('websocket-error', data)
621
+ ),
622
+ client.onMessage('websocket-connection-status-changed', (data) =>
623
+ handleEvent('websocket-connection-status-changed', data)
624
+ ),
625
+ client.onMessage('sse-open', (data) =>
626
+ handleEvent('sse-open', data)
627
+ ),
628
+ client.onMessage('sse-message', (data) =>
629
+ handleEvent('sse-message', data)
630
+ ),
631
+ client.onMessage('sse-error', (data) =>
632
+ handleEvent('sse-error', data)
633
+ ),
634
+ client.onMessage('sse-close', (data) =>
635
+ handleEvent('sse-close', data)
636
+ ),
637
+ ];
638
+
639
+ // Store unsubscribe functions in the state for cleanup
640
+ set({
641
+ _unsubscribeFunctions: unsubscribeFunctions,
642
+ _client: client,
643
+ });
644
+
645
+ // Request client UI settings from React Native side
646
+ client.send('get-client-ui-settings', {});
647
+ },
648
+
649
+ cleanupClient: () => {
650
+ const { _unsubscribeFunctions, _client } = get();
651
+
652
+ if (_unsubscribeFunctions) {
653
+ _unsubscribeFunctions.forEach(
654
+ (unsubscribe: { remove: () => void }) => unsubscribe.remove()
655
+ );
656
+ }
657
+
658
+ if (_client) {
659
+ _client.send('network-disable', {});
660
+ }
661
+
662
+ set({
663
+ _unsubscribeFunctions: undefined,
664
+ _client: undefined,
665
+ });
666
+ },
667
+ },
668
+ }),
669
+ {
670
+ name: 'rozenite-network-activity-storage',
671
+ version: STORE_VERSION,
672
+ storage: createJSONStorage(() => localStorage, {
673
+ replacer: (key, value) => {
674
+ if (value instanceof Map) {
675
+ return {
676
+ _type: 'map',
677
+ value: Array.from(value.entries()),
678
+ };
679
+ }
680
+ return value;
681
+ },
682
+ reviver: (key, value) => {
683
+ if (
684
+ typeof value === 'object' &&
685
+ value !== null &&
686
+ '_type' in value &&
687
+ value._type === 'map'
688
+ ) {
689
+ return new Map(value.value);
690
+ }
691
+ return value;
692
+ },
693
+ }),
694
+ partialize: (state) => ({ overrides: state.overrides }), // Persist only the overrides
473
695
  }
474
- },
475
-
476
- // Client management
477
- client: {
478
- setupClient: (client: NetworkActivityDevToolsClient) => {
479
- const { handleEvent } = get();
480
-
481
- // Subscribe to all events using the unified handler
482
- const unsubscribeFunctions = [
483
- client.onMessage('request-sent', (data) =>
484
- handleEvent('request-sent', data)
485
- ),
486
- client.onMessage('response-received', (data) =>
487
- handleEvent('response-received', data)
488
- ),
489
- client.onMessage('request-completed', (data) =>
490
- handleEvent('request-completed', data)
491
- ),
492
- client.onMessage('request-failed', (data) =>
493
- handleEvent('request-failed', data)
494
- ),
495
- client.onMessage('response-body', (data) =>
496
- handleEvent('response-body', data)
497
- ),
498
- client.onMessage('websocket-connect', (data) =>
499
- handleEvent('websocket-connect', data)
500
- ),
501
- client.onMessage('websocket-open', (data) =>
502
- handleEvent('websocket-open', data)
503
- ),
504
- client.onMessage('websocket-close', (data) =>
505
- handleEvent('websocket-close', data)
506
- ),
507
- client.onMessage('websocket-message-sent', (data) =>
508
- handleEvent('websocket-message-sent', data)
509
- ),
510
- client.onMessage('websocket-message-received', (data) =>
511
- handleEvent('websocket-message-received', data)
512
- ),
513
- client.onMessage('websocket-error', (data) =>
514
- handleEvent('websocket-error', data)
515
- ),
516
- client.onMessage('websocket-connection-status-changed', (data) =>
517
- handleEvent('websocket-connection-status-changed', data)
518
- ),
519
- client.onMessage('sse-open', (data) => handleEvent('sse-open', data)),
520
- client.onMessage('sse-message', (data) =>
521
- handleEvent('sse-message', data)
522
- ),
523
- client.onMessage('sse-error', (data) =>
524
- handleEvent('sse-error', data)
525
- ),
526
- client.onMessage('sse-close', (data) =>
527
- handleEvent('sse-close', data)
528
- ),
529
- ];
530
-
531
- // Store unsubscribe functions in the state for cleanup
532
- set({
533
- _unsubscribeFunctions: unsubscribeFunctions,
534
- _client: client,
535
- });
536
- },
537
-
538
- cleanupClient: () => {
539
- const { _unsubscribeFunctions, _client } = get();
540
-
541
- if (_unsubscribeFunctions) {
542
- _unsubscribeFunctions.forEach((unsubscribe: { remove: () => void }) =>
543
- unsubscribe.remove()
544
- );
545
- }
546
-
547
- if (_client) {
548
- _client.send('network-disable', {});
549
- }
550
-
551
- set({
552
- _unsubscribeFunctions: undefined,
553
- _client: undefined,
554
- });
555
- },
556
- },
557
- }));
696
+ )
697
+ );
558
698
 
559
699
  export const store = createNetworkActivityStore();