@rozenite/network-activity-plugin 1.7.0-rc.2 → 1.8.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 (43) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +24 -0
  3. package/dist/devtools/App.html +2 -2
  4. package/dist/devtools/assets/{App-pokLiGYV.js → App-B3xlUjs6.js} +163 -115
  5. package/dist/devtools/assets/{App-BrSkOkws.css → App-m6xge0az.css} +13 -0
  6. package/dist/react-native/chunks/boot-recording.cjs +307 -28
  7. package/dist/react-native/chunks/boot-recording.js +310 -31
  8. package/dist/react-native/chunks/useNetworkActivityDevTools.require.cjs +192 -141
  9. package/dist/react-native/chunks/useNetworkActivityDevTools.require.js +193 -142
  10. package/dist/react-native/index.d.ts +24 -7
  11. package/dist/rozenite.json +1 -1
  12. package/dist/sdk/index.cjs +127 -0
  13. package/dist/sdk/index.d.ts +1243 -0
  14. package/dist/sdk/index.js +127 -0
  15. package/package.json +17 -6
  16. package/sdk.ts +59 -0
  17. package/src/react-native/__tests__/events-listener.test.ts +35 -0
  18. package/src/react-native/agent/__tests__/network-activity-agent-state.test.ts +21 -9
  19. package/src/react-native/agent/state.ts +13 -13
  20. package/src/react-native/agent/tools.ts +20 -146
  21. package/src/react-native/agent/use-network-activity-agent-tools.ts +73 -64
  22. package/src/react-native/events-listener.ts +12 -3
  23. package/src/react-native/http/http-inspector.ts +19 -5
  24. package/src/react-native/network-inspector.ts +46 -8
  25. package/src/react-native/nitro-fetch/__tests__/nitro-network-inspector.test.ts +198 -0
  26. package/src/react-native/nitro-fetch/nitro-network-inspector.ts +403 -0
  27. package/src/react-native/useHttpInspector.ts +9 -19
  28. package/src/react-native/useNetworkActivityDevTools.ts +13 -1
  29. package/src/react-native/websocket/__tests__/websocket-inspector.test.ts +69 -0
  30. package/src/react-native/websocket/websocket-inspector.ts +32 -17
  31. package/src/shared/agent-tools.ts +230 -0
  32. package/src/shared/client.ts +3 -0
  33. package/src/shared/http-events.ts +6 -0
  34. package/src/shared/websocket-events.ts +16 -7
  35. package/src/ui/components/RequestList.tsx +21 -0
  36. package/src/ui/components/SidePanel.tsx +12 -9
  37. package/src/ui/state/derived.ts +4 -0
  38. package/src/ui/state/model.ts +6 -1
  39. package/src/ui/state/store.ts +52 -36
  40. package/src/ui/tabs/HeadersTab.tsx +18 -4
  41. package/src/ui/tabs/ResponseTab.tsx +5 -3
  42. package/tsconfig.json +4 -1
  43. package/vite.config.ts +7 -0
@@ -0,0 +1,198 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createNitroNetworkInspector } from '../nitro-network-inspector';
3
+
4
+ describe('nitro network inspector', () => {
5
+ it('translates nitro websocket updates with direct string ids and no duplicate messages', () => {
6
+ const listeners = new Set<(entry: any) => void>();
7
+ const inspector = createNitroNetworkInspector(() => ({
8
+ NetworkInspector: {
9
+ enable() {},
10
+ disable() {},
11
+ isEnabled() {
12
+ return true;
13
+ },
14
+ onEntry(callback) {
15
+ listeners.add(callback);
16
+ return () => listeners.delete(callback);
17
+ },
18
+ getEntries() {
19
+ return [];
20
+ },
21
+ },
22
+ }));
23
+
24
+ const events: Array<{ type: string; socketId?: string; data?: string }> =
25
+ [];
26
+ inspector.on('websocket-connect', (event) => {
27
+ events.push({ type: event.type, socketId: event.socketId });
28
+ });
29
+ inspector.on('websocket-open', (event) => {
30
+ events.push({ type: event.type, socketId: event.socketId });
31
+ });
32
+ inspector.on('websocket-message-sent', (event) => {
33
+ events.push({
34
+ type: event.type,
35
+ socketId: event.socketId,
36
+ data: event.data,
37
+ });
38
+ });
39
+ inspector.on('websocket-message-received', (event) => {
40
+ events.push({
41
+ type: event.type,
42
+ socketId: event.socketId,
43
+ data: event.data,
44
+ });
45
+ });
46
+ inspector.on('websocket-close', (event) => {
47
+ events.push({ type: event.type, socketId: event.socketId });
48
+ });
49
+
50
+ inspector.enable();
51
+
52
+ const emit = (entry: any) => {
53
+ for (const listener of listeners) {
54
+ listener(entry);
55
+ }
56
+ };
57
+
58
+ emit({
59
+ id: 'nitro-ws-1',
60
+ type: 'websocket',
61
+ url: 'wss://example.com/socket',
62
+ protocols: ['chat'],
63
+ requestHeaders: [],
64
+ startTime: 10,
65
+ endTime: 0,
66
+ duration: 0,
67
+ readyState: 'OPEN',
68
+ messages: [
69
+ {
70
+ direction: 'sent',
71
+ data: 'ping',
72
+ size: 4,
73
+ isBinary: false,
74
+ timestamp: 11,
75
+ },
76
+ ],
77
+ messagesSent: 1,
78
+ messagesReceived: 0,
79
+ bytesSent: 4,
80
+ bytesReceived: 0,
81
+ });
82
+
83
+ emit({
84
+ id: 'nitro-ws-1',
85
+ type: 'websocket',
86
+ url: 'wss://example.com/socket',
87
+ protocols: ['chat'],
88
+ requestHeaders: [],
89
+ startTime: 10,
90
+ endTime: 0,
91
+ duration: 0,
92
+ readyState: 'OPEN',
93
+ messages: [
94
+ {
95
+ direction: 'sent',
96
+ data: 'ping',
97
+ size: 4,
98
+ isBinary: false,
99
+ timestamp: 11,
100
+ },
101
+ ],
102
+ messagesSent: 1,
103
+ messagesReceived: 0,
104
+ bytesSent: 4,
105
+ bytesReceived: 0,
106
+ });
107
+
108
+ emit({
109
+ id: 'nitro-ws-1',
110
+ type: 'websocket',
111
+ url: 'wss://example.com/socket',
112
+ protocols: ['chat'],
113
+ requestHeaders: [],
114
+ startTime: 10,
115
+ endTime: 15,
116
+ duration: 5,
117
+ readyState: 'CLOSED',
118
+ messages: [
119
+ {
120
+ direction: 'sent',
121
+ data: 'ping',
122
+ size: 4,
123
+ isBinary: false,
124
+ timestamp: 11,
125
+ },
126
+ {
127
+ direction: 'received',
128
+ data: 'pong',
129
+ size: 4,
130
+ isBinary: false,
131
+ timestamp: 12,
132
+ },
133
+ ],
134
+ messagesSent: 1,
135
+ messagesReceived: 1,
136
+ bytesSent: 4,
137
+ bytesReceived: 4,
138
+ closeCode: 1000,
139
+ closeReason: 'done',
140
+ });
141
+
142
+ expect(events).toEqual([
143
+ { type: 'websocket-connect', socketId: 'nitro-ws-1' },
144
+ { type: 'websocket-open', socketId: 'nitro-ws-1' },
145
+ { type: 'websocket-message-sent', socketId: 'nitro-ws-1', data: 'ping' },
146
+ {
147
+ type: 'websocket-message-received',
148
+ socketId: 'nitro-ws-1',
149
+ data: 'pong',
150
+ },
151
+ { type: 'websocket-close', socketId: 'nitro-ws-1' },
152
+ ]);
153
+ });
154
+
155
+ it('caches nitro http response bodies for later lookup', () => {
156
+ const listeners = new Set<(entry: any) => void>();
157
+ const inspector = createNitroNetworkInspector(() => ({
158
+ NetworkInspector: {
159
+ enable() {},
160
+ disable() {},
161
+ isEnabled() {
162
+ return true;
163
+ },
164
+ onEntry(callback) {
165
+ listeners.add(callback);
166
+ return () => listeners.delete(callback);
167
+ },
168
+ getEntries() {
169
+ return [];
170
+ },
171
+ },
172
+ }));
173
+
174
+ inspector.enable();
175
+
176
+ for (const listener of listeners) {
177
+ listener({
178
+ id: 'nitro-http-1',
179
+ type: 'http',
180
+ url: 'https://example.com/api',
181
+ method: 'GET',
182
+ requestHeaders: [],
183
+ requestBody: undefined,
184
+ requestBodySize: 0,
185
+ status: 200,
186
+ statusText: 'OK',
187
+ responseHeaders: [{ key: 'content-type', value: 'application/json' }],
188
+ responseBody: '{"ok":true}',
189
+ responseBodySize: 11,
190
+ startTime: 10,
191
+ endTime: 20,
192
+ duration: 10,
193
+ });
194
+ }
195
+
196
+ expect(inspector.getResponseBody('nitro-http-1')).toBe('{"ok":true}');
197
+ });
198
+ });
@@ -0,0 +1,403 @@
1
+ import { createNanoEvents } from 'nanoevents';
2
+ import type {
3
+ HttpEventMap,
4
+ HttpHeaders,
5
+ HttpMethod,
6
+ RequestPostData,
7
+ } from '../../shared/client';
8
+ import type { WebSocketEventMap } from '../../shared/websocket-events';
9
+ import type { Inspector } from '../inspector';
10
+
11
+ type NitroHttpHeader = {
12
+ key: string;
13
+ value: string;
14
+ };
15
+
16
+ type NitroHttpEntry = {
17
+ id: string;
18
+ type: 'http';
19
+ url: string;
20
+ method: string;
21
+ requestHeaders: NitroHttpHeader[];
22
+ requestBody?: string;
23
+ requestBodySize: number;
24
+ status: number;
25
+ statusText: string;
26
+ responseHeaders: NitroHttpHeader[];
27
+ responseBody?: string;
28
+ responseBodySize: number;
29
+ startTime: number;
30
+ endTime: number;
31
+ duration: number;
32
+ error?: string;
33
+ };
34
+
35
+ type NitroWebSocketMessage = {
36
+ direction: 'sent' | 'received';
37
+ data: string;
38
+ size: number;
39
+ isBinary: boolean;
40
+ timestamp: number;
41
+ };
42
+
43
+ type NitroWebSocketEntry = {
44
+ id: string;
45
+ type: 'websocket';
46
+ url: string;
47
+ protocols: string[];
48
+ requestHeaders: NitroHttpHeader[];
49
+ startTime: number;
50
+ endTime: number;
51
+ duration: number;
52
+ readyState: string;
53
+ messages: NitroWebSocketMessage[];
54
+ messagesSent: number;
55
+ messagesReceived: number;
56
+ bytesSent: number;
57
+ bytesReceived: number;
58
+ closeCode?: number;
59
+ closeReason?: string;
60
+ error?: string;
61
+ };
62
+
63
+ type NitroInspectorEntry = NitroHttpEntry | NitroWebSocketEntry;
64
+
65
+ type NitroModule = {
66
+ NetworkInspector: {
67
+ enable: () => void;
68
+ disable: () => void;
69
+ isEnabled: () => boolean;
70
+ onEntry: (callback: (entry: NitroInspectorEntry) => void) => () => void;
71
+ getEntries: () => ReadonlyArray<NitroInspectorEntry>;
72
+ };
73
+ };
74
+
75
+ type NitroNetworkEventMap = Pick<
76
+ HttpEventMap & WebSocketEventMap,
77
+ | 'request-sent'
78
+ | 'response-received'
79
+ | 'request-completed'
80
+ | 'request-failed'
81
+ | 'websocket-connect'
82
+ | 'websocket-open'
83
+ | 'websocket-close'
84
+ | 'websocket-message-sent'
85
+ | 'websocket-message-received'
86
+ | 'websocket-error'
87
+ >;
88
+
89
+ type NanoEventsMap = {
90
+ [K in keyof NitroNetworkEventMap]: (data: NitroNetworkEventMap[K]) => void;
91
+ };
92
+
93
+ export type NitroNetworkInspector = Inspector<NitroNetworkEventMap> & {
94
+ getResponseBody: (requestId: string) => string | null;
95
+ };
96
+
97
+ export const NITRO_NETWORK_EVENTS: (keyof NitroNetworkEventMap)[] = [
98
+ 'request-sent',
99
+ 'response-received',
100
+ 'request-completed',
101
+ 'request-failed',
102
+ 'websocket-connect',
103
+ 'websocket-open',
104
+ 'websocket-close',
105
+ 'websocket-message-sent',
106
+ 'websocket-message-received',
107
+ 'websocket-error',
108
+ ];
109
+
110
+ const loadNitroModule = (): NitroModule | null => {
111
+ try {
112
+ return require('react-native-nitro-fetch') as NitroModule;
113
+ } catch {
114
+ return null;
115
+ }
116
+ };
117
+
118
+ const timestampOrigin =
119
+ typeof performance !== 'undefined' &&
120
+ typeof performance.timeOrigin === 'number'
121
+ ? performance.timeOrigin
122
+ : Date.now() - performance.now();
123
+
124
+ const toEpochTime = (timestamp: number) =>
125
+ Math.round(timestampOrigin + timestamp);
126
+
127
+ const toHeaders = (headers: NitroHttpHeader[]): HttpHeaders => {
128
+ return headers.reduce<HttpHeaders>((acc, { key, value }) => {
129
+ const existing = acc[key];
130
+ if (existing === undefined) {
131
+ acc[key] = value;
132
+ return acc;
133
+ }
134
+
135
+ acc[key] = Array.isArray(existing)
136
+ ? [...existing, value]
137
+ : [existing, value];
138
+ return acc;
139
+ }, {});
140
+ };
141
+
142
+ const toPostData = (body?: string): RequestPostData => {
143
+ if (body == null) {
144
+ return undefined;
145
+ }
146
+
147
+ return {
148
+ type: 'text',
149
+ value: body,
150
+ };
151
+ };
152
+
153
+ const cloneEntry = <TEntry extends NitroInspectorEntry>(
154
+ entry: TEntry,
155
+ ): TEntry => {
156
+ return JSON.parse(JSON.stringify(entry)) as TEntry;
157
+ };
158
+
159
+ const getContentType = (headers: NitroHttpHeader[]) => {
160
+ return (
161
+ headers.find((header) => header.key.toLowerCase() === 'content-type')
162
+ ?.value ?? 'text/plain'
163
+ );
164
+ };
165
+
166
+ const normalizeReadyState = (readyState: string) => readyState.toUpperCase();
167
+
168
+ export const createNitroNetworkInspector = (
169
+ getNitroModule: () => NitroModule | null = loadNitroModule,
170
+ ): NitroNetworkInspector => {
171
+ const eventEmitter = createNanoEvents<NanoEventsMap>();
172
+ const previousEntries = new Map<string, NitroInspectorEntry>();
173
+ const responseBodies = new Map<string, string | null>();
174
+ let nitroModule: NitroModule | null = null;
175
+ let unsubscribe: (() => void) | null = null;
176
+
177
+ const emitHttpEvents = (entry: NitroHttpEntry, previous?: NitroHttpEntry) => {
178
+ if (!previous) {
179
+ eventEmitter.emit('request-sent', {
180
+ requestId: entry.id,
181
+ timestamp: toEpochTime(entry.startTime),
182
+ request: {
183
+ url: entry.url,
184
+ method: entry.method as HttpMethod,
185
+ headers: toHeaders(entry.requestHeaders),
186
+ postData: toPostData(entry.requestBody),
187
+ },
188
+ initiator: { type: 'other' },
189
+ type: 'Fetch',
190
+ source: 'nitro',
191
+ });
192
+ }
193
+
194
+ if (entry.error) {
195
+ if (!previous || previous.error !== entry.error) {
196
+ eventEmitter.emit('request-failed', {
197
+ requestId: entry.id,
198
+ timestamp: toEpochTime(entry.endTime || entry.startTime),
199
+ type: 'Fetch',
200
+ error: entry.error,
201
+ canceled: entry.error === 'Request canceled',
202
+ source: 'nitro',
203
+ });
204
+ }
205
+ return;
206
+ }
207
+
208
+ const didResponseChange =
209
+ !previous ||
210
+ previous.status !== entry.status ||
211
+ previous.statusText !== entry.statusText ||
212
+ previous.responseBodySize !== entry.responseBodySize ||
213
+ previous.endTime !== entry.endTime;
214
+
215
+ if (!didResponseChange) {
216
+ return;
217
+ }
218
+
219
+ const responseTimestamp = toEpochTime(entry.endTime || entry.startTime);
220
+
221
+ eventEmitter.emit('response-received', {
222
+ requestId: entry.id,
223
+ timestamp: responseTimestamp,
224
+ type: 'Fetch',
225
+ response: {
226
+ url: entry.url,
227
+ status: entry.status,
228
+ statusText: entry.statusText,
229
+ headers: toHeaders(entry.responseHeaders),
230
+ contentType: getContentType(entry.responseHeaders),
231
+ size: entry.responseBodySize,
232
+ responseTime: responseTimestamp,
233
+ },
234
+ source: 'nitro',
235
+ });
236
+
237
+ eventEmitter.emit('request-completed', {
238
+ requestId: entry.id,
239
+ timestamp: responseTimestamp,
240
+ duration: entry.duration,
241
+ size: entry.responseBodySize,
242
+ ttfb: entry.duration,
243
+ source: 'nitro',
244
+ });
245
+ };
246
+
247
+ const emitWebSocketEvents = (
248
+ entry: NitroWebSocketEntry,
249
+ previous?: NitroWebSocketEntry,
250
+ ) => {
251
+ const socketId = entry.id;
252
+ const readyState = normalizeReadyState(entry.readyState);
253
+ const previousReadyState = previous
254
+ ? normalizeReadyState(previous.readyState)
255
+ : null;
256
+
257
+ if (!previous) {
258
+ eventEmitter.emit('websocket-connect', {
259
+ type: 'websocket-connect',
260
+ url: entry.url,
261
+ socketId,
262
+ timestamp: toEpochTime(entry.startTime),
263
+ protocols: entry.protocols,
264
+ options: [],
265
+ source: 'nitro',
266
+ });
267
+ }
268
+
269
+ if (readyState === 'OPEN' && previousReadyState !== 'OPEN') {
270
+ eventEmitter.emit('websocket-open', {
271
+ type: 'websocket-open',
272
+ url: entry.url,
273
+ socketId,
274
+ timestamp: toEpochTime(entry.startTime),
275
+ source: 'nitro',
276
+ });
277
+ }
278
+
279
+ const previousMessageCount = previous?.messages.length ?? 0;
280
+ for (const message of entry.messages.slice(previousMessageCount)) {
281
+ const event = {
282
+ url: entry.url,
283
+ socketId,
284
+ timestamp: toEpochTime(message.timestamp),
285
+ data: message.data,
286
+ messageType: message.isBinary ? ('binary' as const) : ('text' as const),
287
+ source: 'nitro' as const,
288
+ };
289
+
290
+ if (message.direction === 'sent') {
291
+ eventEmitter.emit('websocket-message-sent', {
292
+ type: 'websocket-message-sent',
293
+ ...event,
294
+ });
295
+ } else {
296
+ eventEmitter.emit('websocket-message-received', {
297
+ type: 'websocket-message-received',
298
+ ...event,
299
+ });
300
+ }
301
+ }
302
+
303
+ if (entry.error && (!previous || previous.error !== entry.error)) {
304
+ eventEmitter.emit('websocket-error', {
305
+ type: 'websocket-error',
306
+ url: entry.url,
307
+ socketId,
308
+ timestamp: toEpochTime(entry.endTime || entry.startTime),
309
+ error: entry.error,
310
+ source: 'nitro',
311
+ });
312
+ }
313
+
314
+ if (readyState === 'CLOSED' && previousReadyState !== 'CLOSED') {
315
+ eventEmitter.emit('websocket-close', {
316
+ type: 'websocket-close',
317
+ url: entry.url,
318
+ socketId,
319
+ timestamp: toEpochTime(entry.endTime || entry.startTime),
320
+ code: entry.closeCode ?? 0,
321
+ reason: entry.closeReason,
322
+ source: 'nitro',
323
+ });
324
+ }
325
+ };
326
+
327
+ const handleEntry = (entry: NitroInspectorEntry) => {
328
+ const previous = previousEntries.get(entry.id);
329
+
330
+ if (entry.type === 'http') {
331
+ responseBodies.set(entry.id, entry.responseBody ?? null);
332
+ emitHttpEvents(entry, previous as NitroHttpEntry | undefined);
333
+ } else {
334
+ emitWebSocketEvents(entry, previous as NitroWebSocketEntry | undefined);
335
+ }
336
+
337
+ previousEntries.set(entry.id, cloneEntry(entry));
338
+ };
339
+
340
+ return {
341
+ enable() {
342
+ if (unsubscribe) {
343
+ return;
344
+ }
345
+
346
+ nitroModule = getNitroModule();
347
+ if (!nitroModule) {
348
+ return;
349
+ }
350
+
351
+ nitroModule.NetworkInspector.enable();
352
+ for (const entry of nitroModule.NetworkInspector.getEntries()) {
353
+ previousEntries.set(entry.id, cloneEntry(entry));
354
+ if (entry.type === 'http') {
355
+ responseBodies.set(entry.id, entry.responseBody ?? null);
356
+ }
357
+ }
358
+ unsubscribe = nitroModule.NetworkInspector.onEntry(handleEntry);
359
+ },
360
+
361
+ disable() {
362
+ unsubscribe?.();
363
+ unsubscribe = null;
364
+ nitroModule?.NetworkInspector.disable();
365
+ },
366
+
367
+ isEnabled() {
368
+ return nitroModule?.NetworkInspector.isEnabled() ?? false;
369
+ },
370
+
371
+ dispose() {
372
+ unsubscribe?.();
373
+ unsubscribe = null;
374
+ previousEntries.clear();
375
+ responseBodies.clear();
376
+ nitroModule?.NetworkInspector.disable();
377
+ nitroModule = null;
378
+ },
379
+
380
+ getResponseBody(requestId: string) {
381
+ return responseBodies.get(requestId) ?? null;
382
+ },
383
+
384
+ on<TEventType extends keyof NitroNetworkEventMap>(
385
+ event: TEventType,
386
+ callback: (data: NitroNetworkEventMap[TEventType]) => void,
387
+ ) {
388
+ return eventEmitter.on(event, callback as NanoEventsMap[TEventType]);
389
+ },
390
+ };
391
+ };
392
+
393
+ export const getNitroNetworkInspector = (() => {
394
+ let instance: NitroNetworkInspector | null = null;
395
+
396
+ return (): NitroNetworkInspector => {
397
+ if (!instance) {
398
+ instance = createNitroNetworkInspector();
399
+ }
400
+
401
+ return instance;
402
+ };
403
+ })();
@@ -1,43 +1,33 @@
1
1
  import { useEffect } from 'react';
2
- import type { HTTPInspector } from './http/http-inspector';
2
+ import type { NetworkInspector } from './network-inspector';
3
3
  import type { NetworkActivityDevToolsClient } from '../shared/client';
4
- import { getResponseBody } from './http/http-utils';
5
4
  import { getOverridesRegistry } from './http/overrides-registry';
6
5
 
7
6
  const overridesRegistry = getOverridesRegistry();
8
7
 
9
8
  export const useHttpInspector = (
10
9
  client: NetworkActivityDevToolsClient | null,
11
- httpInspector: HTTPInspector,
10
+ networkInspector: NetworkInspector,
12
11
  isEnabled: boolean,
13
- isRecordingEnabled: boolean
12
+ isRecordingEnabled: boolean,
14
13
  ) => {
15
14
  useEffect(() => {
16
15
  if (!client || !isEnabled) {
17
16
  return;
18
17
  }
19
18
 
20
- const networkRequestsRegistry =
21
- httpInspector.getNetworkRequestsRegistry();
22
-
23
19
  const subscriptions = [
24
20
  client.onMessage('network-enable', () => {
25
- httpInspector.enable();
21
+ networkInspector.http.enable();
26
22
  }),
27
23
  client.onMessage('network-disable', () => {
28
- httpInspector.disable();
24
+ networkInspector.http.disable();
29
25
  }),
30
26
  client.onMessage('set-overrides', (data) => {
31
27
  overridesRegistry.setOverrides(data.overrides);
32
28
  }),
33
29
  client.onMessage('get-response-body', async ({ requestId }) => {
34
- const request = networkRequestsRegistry.getEntry(requestId);
35
-
36
- if (!request) {
37
- return;
38
- }
39
-
40
- const body = await getResponseBody(request);
30
+ const body = await networkInspector.getResponseBody(requestId);
41
31
 
42
32
  client.send('response-body', {
43
33
  requestId,
@@ -48,12 +38,12 @@ export const useHttpInspector = (
48
38
 
49
39
  // If recording was previously enabled, enable the inspector (hot reload)
50
40
  if (isRecordingEnabled) {
51
- httpInspector.enable();
41
+ networkInspector.http.enable();
52
42
  }
53
43
 
54
44
  return () => {
55
45
  subscriptions.forEach((subscription) => subscription.remove());
56
- httpInspector.dispose();
46
+ networkInspector.http.dispose();
57
47
  };
58
- }, [client, httpInspector, isEnabled, isRecordingEnabled]);
48
+ }, [client, networkInspector, isEnabled, isRecordingEnabled]);
59
49
  };
@@ -67,6 +67,11 @@ export const useNetworkActivityDevTools = (
67
67
  const subscriptions = [
68
68
  client.onMessage('network-enable', () => {
69
69
  isRecordingEnabledRef.current = true;
70
+ networkInspector.enable({
71
+ http: isHttpInspectorEnabled,
72
+ websocket: isWebSocketInspectorEnabled,
73
+ sse: isSSEInspectorEnabled,
74
+ });
70
75
 
71
76
  // Connect the events listener to send events through the DevTools client
72
77
  // This also automatically flushes any queued messages
@@ -87,12 +92,19 @@ export const useNetworkActivityDevTools = (
87
92
  }),
88
93
  client.onMessage('network-disable', () => {
89
94
  isRecordingEnabledRef.current = false;
95
+ networkInspector.disable();
90
96
  }),
91
97
  client.onMessage('get-client-ui-settings', () => {
92
98
  sendClientUISettings();
93
99
  }),
94
100
  ];
95
101
 
102
+ // Inform the DevTools UI of the current recording state so it can detect
103
+ // and resolve desynchronization (e.g. after an app reload)
104
+ client.send('recording-state', {
105
+ isRecording: isRecordingEnabledRef.current,
106
+ });
107
+
96
108
  // Send initial or changed values live
97
109
  sendClientUISettings();
98
110
 
@@ -109,7 +121,7 @@ export const useNetworkActivityDevTools = (
109
121
 
110
122
  useHttpInspector(
111
123
  client,
112
- networkInspector.http,
124
+ networkInspector,
113
125
  isHttpInspectorEnabled,
114
126
  isRecordingEnabledRef.current,
115
127
  );