@rozenite/network-activity-plugin 1.0.0-alpha.15 → 1.0.0-alpha.16

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