@rozenite/network-activity-plugin 1.0.0-alpha.1 → 1.0.0-alpha.10

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 (134) hide show
  1. package/README.md +3 -5
  2. package/dist/{panel.html → App.html} +3 -3
  3. package/dist/assets/App-CA1Fbh0I.js +25364 -0
  4. package/dist/assets/App-DoHQsY5s.css +1276 -0
  5. package/dist/event-source.cjs +22 -0
  6. package/dist/event-source.js +23 -0
  7. package/dist/react-native.cjs +8 -1
  8. package/dist/react-native.d.ts +1 -5
  9. package/dist/react-native.js +6 -171
  10. package/dist/rozenite.config.d.ts +7 -0
  11. package/dist/rozenite.json +1 -1
  12. package/dist/src/react-native/http/network-inspector.d.ts +8 -0
  13. package/dist/src/react-native/http/network-requests-registry.d.ts +6 -0
  14. package/dist/src/react-native/http/xhr-interceptor.d.ts +38 -0
  15. package/dist/src/react-native/sse/event-source.d.ts +2 -0
  16. package/dist/src/react-native/sse/sse-inspector.d.ts +9 -0
  17. package/dist/src/react-native/sse/sse-interceptor.d.ts +36 -0
  18. package/dist/src/react-native/sse/types.d.ts +6 -0
  19. package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -0
  20. package/dist/src/react-native/utils.d.ts +6 -0
  21. package/dist/src/react-native/websocket/websocket-inspector.d.ts +9 -0
  22. package/dist/src/react-native/websocket/websocket-interceptor.d.ts +74 -0
  23. package/dist/src/shared/client.d.ts +68 -0
  24. package/dist/src/shared/sse-events.d.ts +35 -0
  25. package/dist/src/shared/websocket-events.d.ts +60 -0
  26. package/dist/src/ui/App.d.ts +1 -0
  27. package/dist/src/ui/components/Badge.d.ts +9 -0
  28. package/dist/src/ui/components/Button.d.ts +11 -0
  29. package/dist/src/ui/components/Input.d.ts +3 -0
  30. package/dist/src/ui/components/JsonTree.d.ts +5 -0
  31. package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
  32. package/dist/src/ui/components/RequestList.d.ts +25 -0
  33. package/dist/src/ui/components/ScrollArea.d.ts +4 -0
  34. package/dist/src/ui/components/Separator.d.ts +3 -0
  35. package/dist/src/ui/components/SidePanel.d.ts +1 -0
  36. package/dist/src/ui/components/Toolbar.d.ts +1 -0
  37. package/dist/src/ui/hooks/useCopyToClipboard.d.ts +4 -0
  38. package/dist/src/ui/state/derived.d.ts +5 -0
  39. package/dist/src/ui/state/hooks.d.ts +17 -0
  40. package/dist/src/ui/state/model.d.ts +98 -0
  41. package/dist/src/ui/state/store.d.ts +24 -0
  42. package/dist/src/ui/tabs/CookiesTab.d.ts +5 -0
  43. package/dist/src/ui/tabs/HeadersTab.d.ts +5 -0
  44. package/dist/src/ui/tabs/MessagesTab.d.ts +5 -0
  45. package/dist/src/ui/tabs/RequestTab.d.ts +5 -0
  46. package/dist/src/ui/tabs/ResponseTab.d.ts +6 -0
  47. package/dist/src/ui/tabs/SSEMessagesTab.d.ts +5 -0
  48. package/dist/src/ui/tabs/TimingTab.d.ts +5 -0
  49. package/dist/src/ui/types.d.ts +26 -0
  50. package/dist/src/ui/utils/assert.d.ts +1 -0
  51. package/dist/src/ui/utils/cn.d.ts +2 -0
  52. package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
  53. package/dist/src/ui/utils/getHttpHeaderValue.d.ts +2 -0
  54. package/dist/src/ui/utils/getId.d.ts +1 -0
  55. package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
  56. package/dist/src/ui/views/InspectorView.d.ts +5 -0
  57. package/dist/src/ui/views/LoadingView.d.ts +1 -0
  58. package/dist/useNetworkActivityDevTools.cjs +759 -0
  59. package/dist/useNetworkActivityDevTools.js +757 -0
  60. package/package.json +31 -10
  61. package/postcss.config.js +6 -0
  62. package/project.json +12 -0
  63. package/react-native.ts +2 -1
  64. package/rozenite.config.ts +2 -2
  65. package/src/css-modules.d.ts +1 -1
  66. package/src/react-native/http/network-inspector.ts +226 -0
  67. package/src/react-native/http/network-requests-registry.ts +52 -0
  68. package/src/react-native/http/xhr-interceptor.ts +211 -0
  69. package/src/react-native/http/xml-request.d.ts +34 -0
  70. package/src/react-native/sse/event-source.ts +25 -0
  71. package/src/react-native/sse/sse-inspector.ts +117 -0
  72. package/src/react-native/sse/sse-interceptor.ts +162 -0
  73. package/src/react-native/sse/types.ts +9 -0
  74. package/src/react-native/useNetworkActivityDevTools.ts +73 -210
  75. package/src/react-native/utils.ts +43 -0
  76. package/src/react-native/websocket/websocket-inspector.ts +180 -0
  77. package/src/react-native/websocket/websocket-interceptor.d.ts +4 -0
  78. package/src/react-native/websocket/websocket-interceptor.ts +166 -0
  79. package/src/shared/client.ts +86 -0
  80. package/src/shared/sse-events.ts +44 -0
  81. package/src/shared/websocket-events.ts +79 -0
  82. package/src/ui/App.tsx +19 -0
  83. package/src/ui/components/Badge.tsx +36 -0
  84. package/src/ui/components/Button.tsx +56 -0
  85. package/src/ui/components/Input.tsx +22 -0
  86. package/src/ui/components/JsonTree.tsx +50 -0
  87. package/src/ui/components/JsonTreeCopyableItem.tsx +33 -0
  88. package/src/ui/components/RequestList.tsx +295 -0
  89. package/src/ui/components/ScrollArea.tsx +48 -0
  90. package/src/ui/components/Separator.tsx +31 -0
  91. package/src/ui/components/SidePanel.tsx +323 -0
  92. package/src/ui/components/Tabs.tsx +55 -0
  93. package/src/ui/components/Toolbar.tsx +45 -0
  94. package/src/ui/globals.css +90 -0
  95. package/src/ui/hooks/useCopyToClipboard.ts +28 -0
  96. package/src/ui/state/derived.ts +112 -0
  97. package/src/ui/state/hooks.ts +44 -0
  98. package/src/ui/state/model.ts +129 -0
  99. package/src/ui/state/store.ts +559 -0
  100. package/src/ui/tabs/CookiesTab.tsx +279 -0
  101. package/src/ui/tabs/HeadersTab.tsx +110 -0
  102. package/src/ui/tabs/MessagesTab.tsx +276 -0
  103. package/src/ui/tabs/RequestTab.tsx +69 -0
  104. package/src/ui/tabs/ResponseTab.tsx +138 -0
  105. package/src/ui/tabs/SSEMessagesTab.tsx +213 -0
  106. package/src/ui/tabs/TimingTab.tsx +60 -0
  107. package/src/ui/types.ts +34 -0
  108. package/src/ui/utils/assert.ts +5 -0
  109. package/src/ui/utils/cn.ts +6 -0
  110. package/src/ui/utils/copyToClipboard.ts +3 -0
  111. package/src/ui/utils/getHttpHeaderValue.ts +14 -0
  112. package/src/ui/utils/getId.ts +10 -0
  113. package/src/ui/utils/getStatusColor.ts +15 -0
  114. package/src/ui/views/InspectorView.tsx +53 -0
  115. package/src/ui/views/LoadingView.tsx +19 -0
  116. package/tailwind.config.ts +96 -0
  117. package/tsconfig.json +13 -6
  118. package/tsconfig.tsbuildinfo +1 -0
  119. package/vite.config.ts +13 -1
  120. package/dist/assets/panel-C5YgUUj5.js +0 -54
  121. package/dist/assets/panel-NCVczPb1.css +0 -1
  122. package/src/types/network.ts +0 -153
  123. package/src/ui/components.module.css +0 -158
  124. package/src/ui/components.tsx +0 -219
  125. package/src/ui/network-details.module.css +0 -57
  126. package/src/ui/network-details.tsx +0 -134
  127. package/src/ui/network-list.module.css +0 -122
  128. package/src/ui/network-list.tsx +0 -145
  129. package/src/ui/network-toolbar.module.css +0 -9
  130. package/src/ui/network-toolbar.tsx +0 -40
  131. package/src/ui/panel.module.css +0 -61
  132. package/src/ui/panel.tsx +0 -201
  133. package/src/ui/tanstack-query.tsx +0 -197
  134. package/src/ui/utils.ts +0 -89
@@ -0,0 +1,129 @@
1
+ import { Initiator, ResourceType } from '../../shared/client';
2
+
3
+ export type RequestId = string;
4
+ export type Timestamp = number;
5
+ export type SocketId = number;
6
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
7
+
8
+ export type NetworkEntryType = 'http' | 'websocket' | 'sse';
9
+
10
+ /* HTTP */
11
+ export type HttpData = {
12
+ type: string;
13
+ data: string;
14
+ };
15
+
16
+ export type HttpRequest = {
17
+ url: string;
18
+ method: HttpMethod;
19
+ headers: Record<string, string>;
20
+ body?: HttpData;
21
+ };
22
+
23
+ export type HttpResponse = {
24
+ url: string;
25
+ status: number;
26
+ statusText: string;
27
+ headers: Record<string, string>;
28
+ contentType: string;
29
+ size: number;
30
+ responseTime: Timestamp;
31
+ body?: HttpData;
32
+ };
33
+
34
+ export type HttpStatus = 'pending' | 'loading' | 'finished' | 'failed';
35
+
36
+ export type HttpNetworkEntry = {
37
+ id: RequestId;
38
+ type: 'http';
39
+ timestamp: Timestamp;
40
+ duration?: number;
41
+
42
+ request: HttpRequest;
43
+ response?: HttpResponse;
44
+ status: HttpStatus;
45
+ error?: string;
46
+ canceled?: boolean;
47
+ ttfb?: number;
48
+ size?: number;
49
+ initiator?: Initiator;
50
+ resourceType?: ResourceType;
51
+ };
52
+
53
+ /* SSE */
54
+ export type SSEMessage = {
55
+ id: string;
56
+ data: string;
57
+ timestamp: Timestamp;
58
+ };
59
+
60
+ export type SSEStatus = 'connecting' | 'open' | 'closed' | 'error';
61
+
62
+ export type SSENetworkEntry = {
63
+ id: RequestId;
64
+ type: 'sse';
65
+ timestamp: Timestamp;
66
+ duration?: number;
67
+
68
+ request: HttpRequest;
69
+ response?: HttpResponse;
70
+ status: SSEStatus;
71
+ messages: SSEMessage[];
72
+ error?: string;
73
+ initiator?: Initiator;
74
+ resourceType?: ResourceType;
75
+ };
76
+
77
+ /* WebSocket */
78
+ export type WebSocketConnection = {
79
+ url: string;
80
+ socketId: SocketId;
81
+ protocols?: string[];
82
+ options?: string[];
83
+ };
84
+
85
+ export type WebSocketMessage = {
86
+ id: string;
87
+ direction: 'sent' | 'received';
88
+ data: string;
89
+ messageType: 'text' | 'binary';
90
+ timestamp: Timestamp;
91
+ };
92
+
93
+ export type WebSocketStatus =
94
+ | 'connecting'
95
+ | 'open'
96
+ | 'closing'
97
+ | 'closed'
98
+ | 'error';
99
+
100
+ export type WebSocketNetworkEntry = {
101
+ id: RequestId;
102
+ type: 'websocket';
103
+ timestamp: Timestamp;
104
+ duration?: number;
105
+
106
+ connection: WebSocketConnection;
107
+ status: WebSocketStatus;
108
+ error?: string;
109
+ closeCode?: number;
110
+ closeReason?: string;
111
+ };
112
+
113
+ /* Shared */
114
+ export type NetworkEntry =
115
+ | HttpNetworkEntry
116
+ | WebSocketNetworkEntry
117
+ | SSENetworkEntry;
118
+
119
+ export type ProcessedRequest = {
120
+ id: RequestId;
121
+ type: NetworkEntryType;
122
+ name: string;
123
+ status: HttpStatus | WebSocketStatus | SSEStatus;
124
+ timestamp: Timestamp;
125
+ duration?: number;
126
+ size?: number;
127
+ method: HttpMethod | 'WS' | 'SSE';
128
+ httpStatus?: number;
129
+ };
@@ -0,0 +1,559 @@
1
+ import { createStore } from 'zustand';
2
+ import {
3
+ NetworkActivityDevToolsClient,
4
+ NetworkActivityEventMap,
5
+ RequestId,
6
+ } from '../../shared/client';
7
+ import {
8
+ NetworkEntry,
9
+ HttpNetworkEntry,
10
+ WebSocketNetworkEntry,
11
+ WebSocketMessage,
12
+ SSENetworkEntry,
13
+ SSEMessage,
14
+ } from './model';
15
+ import { getHttpHeaderValue } from '../utils/getHttpHeaderValue';
16
+ import { getId } from '../utils/getId';
17
+ import { assert } from '../utils/assert';
18
+
19
+ const MAX_WEBSOCKET_MESSAGES_PER_CONNECTION = 32;
20
+ const MAX_SSE_MESSAGES_PER_CONNECTION = 32;
21
+
22
+ export interface NetworkActivityState {
23
+ // State
24
+ isRecording: boolean;
25
+ selectedRequestId: RequestId | null;
26
+ networkEntries: Map<RequestId, NetworkEntry>;
27
+ websocketMessages: Map<RequestId, WebSocketMessage[]>;
28
+
29
+ // Internal state (not exposed in interface)
30
+ _unsubscribeFunctions?: Array<{ remove: () => void }>;
31
+ _client?: NetworkActivityDevToolsClient;
32
+
33
+ // Actions
34
+ actions: {
35
+ setRecording: (isRecording: boolean) => void;
36
+ setSelectedRequest: (requestId: RequestId | null) => void;
37
+ clearRequests: () => void;
38
+ };
39
+
40
+ // Event handling
41
+ handleEvent: <K extends keyof NetworkActivityEventMap>(
42
+ eventType: K,
43
+ data: NetworkActivityEventMap[K]
44
+ ) => void;
45
+
46
+ // Client management
47
+ client: {
48
+ setupClient: (client: NetworkActivityDevToolsClient) => void;
49
+ cleanupClient: () => void;
50
+ };
51
+ }
52
+
53
+ 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
196
+ ? {
197
+ type:
198
+ getHttpHeaderValue(
199
+ httpEntry.response?.headers ?? {},
200
+ 'content-type'
201
+ )?.split(';')[0] || 'text/plain',
202
+ data: eventData.body,
203
+ }
204
+ : 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
424
+ ),
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
+ }
473
+ }
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
+ }));
558
+
559
+ export const store = createNetworkActivityStore();