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