@rozenite/network-activity-plugin 1.5.1 → 1.7.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +2 -0
  3. package/dist/{App.html → devtools/App.html} +2 -2
  4. package/dist/{boot-recording.cjs → react-native/chunks/boot-recording.cjs} +1 -1
  5. package/dist/{boot-recording.js → react-native/chunks/boot-recording.js} +3 -3
  6. package/dist/react-native/chunks/boot-recording.require.cjs +5 -0
  7. package/dist/react-native/chunks/boot-recording.require.js +5 -0
  8. package/dist/react-native/chunks/useNetworkActivityDevTools.require.cjs +1102 -0
  9. package/dist/react-native/chunks/useNetworkActivityDevTools.require.js +1102 -0
  10. package/dist/{react-native.cjs → react-native/index.cjs} +2 -2
  11. package/dist/react-native/index.d.ts +305 -0
  12. package/dist/{react-native.js → react-native/index.js} +2 -2
  13. package/dist/rozenite.json +1 -1
  14. package/package.json +27 -7
  15. package/src/react-native/agent/__tests__/network-activity-agent-state.test.ts +250 -0
  16. package/src/react-native/agent/state.ts +869 -0
  17. package/src/react-native/agent/tools.ts +146 -0
  18. package/src/react-native/agent/use-network-activity-agent-tools.ts +244 -0
  19. package/src/react-native/http/http-inspector.ts +0 -1
  20. package/src/react-native/useNetworkActivityDevTools.ts +11 -0
  21. package/tsconfig.json +3 -0
  22. package/dist/react-native.d.ts +0 -4
  23. package/dist/rozenite.config.d.ts +0 -7
  24. package/dist/src/react-native/boot-recording.d.ts +0 -41
  25. package/dist/src/react-native/config.d.ts +0 -23
  26. package/dist/src/react-native/events-listener.d.ts +0 -44
  27. package/dist/src/react-native/http/http-inspector.d.ts +0 -10
  28. package/dist/src/react-native/http/http-utils.d.ts +0 -15
  29. package/dist/src/react-native/http/network-requests-registry.d.ts +0 -6
  30. package/dist/src/react-native/http/overrides-registry.d.ts +0 -6
  31. package/dist/src/react-native/http/xhr-interceptor.d.ts +0 -44
  32. package/dist/src/react-native/inspector.d.ts +0 -7
  33. package/dist/src/react-native/network-inspector.d.ts +0 -16
  34. package/dist/src/react-native/sse/event-source.d.ts +0 -2
  35. package/dist/src/react-native/sse/sse-inspector.d.ts +0 -6
  36. package/dist/src/react-native/sse/sse-interceptor.d.ts +0 -36
  37. package/dist/src/react-native/sse/types.d.ts +0 -6
  38. package/dist/src/react-native/useHttpInspector.d.ts +0 -3
  39. package/dist/src/react-native/useNetworkActivityDevTools.d.ts +0 -3
  40. package/dist/src/react-native/useSSEInspector.d.ts +0 -3
  41. package/dist/src/react-native/useWebSocketInspector.d.ts +0 -3
  42. package/dist/src/react-native/utils/getBlobName.d.ts +0 -35
  43. package/dist/src/react-native/utils/getFormDataEntries.d.ts +0 -18
  44. package/dist/src/react-native/utils.d.ts +0 -6
  45. package/dist/src/react-native/websocket/websocket-inspector.d.ts +0 -6
  46. package/dist/src/react-native/websocket/websocket-interceptor.d.ts +0 -73
  47. package/dist/src/shared/client.d.ts +0 -17
  48. package/dist/src/shared/http-events.d.ts +0 -106
  49. package/dist/src/shared/sse-events.d.ts +0 -38
  50. package/dist/src/shared/websocket-events.d.ts +0 -60
  51. package/dist/src/ui/App.d.ts +0 -1
  52. package/dist/src/ui/components/Badge.d.ts +0 -9
  53. package/dist/src/ui/components/Button.d.ts +0 -11
  54. package/dist/src/ui/components/CodeBlock.d.ts +0 -3
  55. package/dist/src/ui/components/CodeEditor.d.ts +0 -5
  56. package/dist/src/ui/components/CookieCard.d.ts +0 -7
  57. package/dist/src/ui/components/CopyRequestDropdown.d.ts +0 -7
  58. package/dist/src/ui/components/DropdownMenu.d.ts +0 -27
  59. package/dist/src/ui/components/FilterBar.d.ts +0 -10
  60. package/dist/src/ui/components/Input.d.ts +0 -3
  61. package/dist/src/ui/components/JsonTree.d.ts +0 -5
  62. package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +0 -7
  63. package/dist/src/ui/components/KeyValueGrid.d.ts +0 -13
  64. package/dist/src/ui/components/OverrideResponse.d.ts +0 -8
  65. package/dist/src/ui/components/RequestBody.d.ts +0 -6
  66. package/dist/src/ui/components/RequestList.d.ts +0 -30
  67. package/dist/src/ui/components/ScrollArea.d.ts +0 -5
  68. package/dist/src/ui/components/Section.d.ts +0 -8
  69. package/dist/src/ui/components/Separator.d.ts +0 -4
  70. package/dist/src/ui/components/SidePanel.d.ts +0 -1
  71. package/dist/src/ui/components/Tabs.d.ts +0 -7
  72. package/dist/src/ui/components/Toolbar.d.ts +0 -1
  73. package/dist/src/ui/hooks/useCopyToClipboard.d.ts +0 -4
  74. package/dist/src/ui/state/derived.d.ts +0 -5
  75. package/dist/src/ui/state/hooks.d.ts +0 -21
  76. package/dist/src/ui/state/model.d.ts +0 -113
  77. package/dist/src/ui/state/store.d.ts +0 -48
  78. package/dist/src/ui/tabs/CookiesTab.d.ts +0 -5
  79. package/dist/src/ui/tabs/HeadersTab.d.ts +0 -5
  80. package/dist/src/ui/tabs/MessagesTab.d.ts +0 -5
  81. package/dist/src/ui/tabs/RequestTab.d.ts +0 -5
  82. package/dist/src/ui/tabs/ResponseTab.d.ts +0 -6
  83. package/dist/src/ui/tabs/SSEMessagesTab.d.ts +0 -5
  84. package/dist/src/ui/tabs/TimingTab.d.ts +0 -5
  85. package/dist/src/ui/types.d.ts +0 -26
  86. package/dist/src/ui/utils/assert.d.ts +0 -1
  87. package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +0 -2
  88. package/dist/src/ui/utils/cn.d.ts +0 -2
  89. package/dist/src/ui/utils/copyToClipboard.d.ts +0 -1
  90. package/dist/src/ui/utils/escapeShellArg.d.ts +0 -1
  91. package/dist/src/ui/utils/generateCurlCommand.d.ts +0 -2
  92. package/dist/src/ui/utils/generateFetchCall.d.ts +0 -2
  93. package/dist/src/ui/utils/generateMultipartBody.d.ts +0 -4
  94. package/dist/src/ui/utils/getId.d.ts +0 -1
  95. package/dist/src/ui/utils/getStatusColor.d.ts +0 -1
  96. package/dist/src/ui/views/InspectorView.d.ts +0 -5
  97. package/dist/src/ui/views/LoadingView.d.ts +0 -1
  98. package/dist/src/utils/applyReactNativeRequestHeadersLogic.d.ts +0 -7
  99. package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +0 -9
  100. package/dist/src/utils/cookieParser.d.ts +0 -6
  101. package/dist/src/utils/getContentTypeMimeType.d.ts +0 -2
  102. package/dist/src/utils/getHttpHeader.d.ts +0 -5
  103. package/dist/src/utils/getHttpHeaderValueAsString.d.ts +0 -11
  104. package/dist/src/utils/getStringSizeInBytes.d.ts +0 -1
  105. package/dist/src/utils/inferContentTypeFromPostData.d.ts +0 -2
  106. package/dist/src/utils/safeStringify.d.ts +0 -1
  107. package/dist/src/utils/typeChecks.d.ts +0 -9
  108. package/dist/useNetworkActivityDevTools.cjs +0 -171
  109. package/dist/useNetworkActivityDevTools.js +0 -171
  110. /package/dist/{assets → devtools/assets}/App-BrSkOkws.css +0 -0
  111. /package/dist/{assets/App-CGt4qucR.js → devtools/assets/App-pokLiGYV.js} +0 -0
  112. /package/dist/{event-source.cjs → react-native/chunks/event-source.cjs} +0 -0
  113. /package/dist/{event-source.js → react-native/chunks/event-source.js} +0 -0
@@ -0,0 +1,1102 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useRozeniteDevToolsClient } from "@rozenite/plugin-bridge";
3
+ import { s as safeStringify, g as getResponseBody, a as getOverridesRegistry, c as createNetworkInspectorsConfiguration, D as DEFAULT_CONFIG, v as validateConfig, i as isHttpEvent, b as isWebSocketEvent, d as isSSEEvent } from "./boot-recording.js";
4
+ import { useRozenitePluginAgentTool } from "@rozenite/agent-bridge";
5
+ const DEFAULT_PAGE_LIMIT = 20;
6
+ const MAX_PAGE_LIMIT = 100;
7
+ const HTTP_BUFFER_CAPACITY = 500;
8
+ const REALTIME_BUFFER_CAPACITY = 200;
9
+ const createInitialState = () => ({
10
+ isRecording: false,
11
+ generation: 0,
12
+ httpOrder: [],
13
+ httpRecords: /* @__PURE__ */ new Map(),
14
+ httpTotalRecorded: 0,
15
+ httpEvictedCount: 0,
16
+ httpTruncated: false,
17
+ realtimeOrder: [],
18
+ realtimeRecords: /* @__PURE__ */ new Map(),
19
+ realtimeTotalRecorded: 0,
20
+ realtimeEvictedCount: 0,
21
+ realtimeTruncated: false,
22
+ nextMessageId: 1,
23
+ recordingMetadata: {
24
+ enabledInspectors: {
25
+ http: true,
26
+ websocket: true,
27
+ sse: true
28
+ }
29
+ }
30
+ });
31
+ const getLimit = (value) => {
32
+ if (typeof value !== "number" || !Number.isInteger(value) || !Number.isFinite(value) || value < 1) {
33
+ return DEFAULT_PAGE_LIMIT;
34
+ }
35
+ return Math.min(value, MAX_PAGE_LIMIT);
36
+ };
37
+ const encodeCursor = (scope, index) => {
38
+ return `${scope}:${index}`;
39
+ };
40
+ const decodeCursor = (cursor, scope) => {
41
+ const [cursorScope, rawIndex] = cursor.split(":", 2);
42
+ if (cursorScope !== scope || !rawIndex) {
43
+ throw new Error(
44
+ "Cursor does not match the requested listing. Run the command again."
45
+ );
46
+ }
47
+ const index = Number(rawIndex);
48
+ if (!Number.isInteger(index) || index < 0) {
49
+ throw new Error("Cursor is invalid. Run the command again.");
50
+ }
51
+ return index;
52
+ };
53
+ const paginate = (rows, scope, limit, cursor) => {
54
+ const startIndex = cursor ? decodeCursor(cursor, scope) : 0;
55
+ const endIndex = Math.min(startIndex + limit, rows.length);
56
+ const hasMore = endIndex < rows.length;
57
+ return {
58
+ items: rows.slice(startIndex, endIndex),
59
+ page: {
60
+ limit,
61
+ hasMore,
62
+ ...hasMore ? { nextCursor: encodeCursor(scope, endIndex) } : {}
63
+ }
64
+ };
65
+ };
66
+ const serializeRequestBody = (requestId, postData) => {
67
+ if (!postData) {
68
+ return {
69
+ requestId,
70
+ available: false,
71
+ reason: "No request body is available for this request."
72
+ };
73
+ }
74
+ if (postData.type === "text") {
75
+ return {
76
+ requestId,
77
+ available: true,
78
+ body: postData.value,
79
+ base64Encoded: false
80
+ };
81
+ }
82
+ return {
83
+ requestId,
84
+ available: true,
85
+ body: safeStringify(postData),
86
+ base64Encoded: false
87
+ };
88
+ };
89
+ const createHttpSummary = (record) => ({
90
+ requestId: record.requestId,
91
+ method: record.request.method,
92
+ url: record.request.url,
93
+ status: record.response?.status ?? null,
94
+ type: record.resourceType,
95
+ startTimeMs: record.startTimeMs,
96
+ endTimeMs: record.endTimeMs ?? null,
97
+ durationMs: record.durationMs ?? null,
98
+ transferSize: record.size ?? null,
99
+ encodedDataLength: record.response?.size ?? null,
100
+ outcome: record.status === "failed" ? "failed" : record.status === "finished" ? "success" : "in-flight"
101
+ });
102
+ const getRealtimeSummary = (record) => {
103
+ if (record.kind === "websocket") {
104
+ return {
105
+ requestId: record.requestId,
106
+ kind: record.kind,
107
+ url: record.url,
108
+ status: record.status,
109
+ startedAt: record.startedAt,
110
+ endedAt: record.endedAt ?? null,
111
+ durationMs: record.durationMs ?? null,
112
+ messageCount: record.messages.length,
113
+ error: record.error ?? null,
114
+ closeCode: record.closeCode ?? null
115
+ };
116
+ }
117
+ return {
118
+ requestId: record.requestId,
119
+ kind: record.kind,
120
+ url: record.request?.url ?? record.response?.url ?? null,
121
+ status: record.status,
122
+ startedAt: record.startedAt,
123
+ endedAt: record.endedAt ?? null,
124
+ durationMs: record.durationMs ?? null,
125
+ messageCount: record.messages.length,
126
+ error: record.error ?? null,
127
+ httpStatus: record.response?.status ?? null
128
+ };
129
+ };
130
+ const trimMap = (order, records, capacity) => {
131
+ let evicted = 0;
132
+ while (order.length > capacity) {
133
+ const oldestId = order.shift();
134
+ if (!oldestId) {
135
+ break;
136
+ }
137
+ records.delete(oldestId);
138
+ evicted += 1;
139
+ }
140
+ return evicted;
141
+ };
142
+ const createNetworkActivityAgentState = () => {
143
+ const state = createInitialState();
144
+ const getStatus = () => ({
145
+ recording: {
146
+ isRecording: state.isRecording,
147
+ startedAt: state.startedAt ?? null,
148
+ stoppedAt: state.stoppedAt ?? null,
149
+ httpRequestCount: state.httpOrder.length,
150
+ realtimeConnectionCount: state.realtimeOrder.length,
151
+ http: {
152
+ totalRecorded: state.httpTotalRecorded,
153
+ evictedCount: state.httpEvictedCount,
154
+ truncated: state.httpTruncated,
155
+ capacity: HTTP_BUFFER_CAPACITY
156
+ },
157
+ realtime: {
158
+ totalRecorded: state.realtimeTotalRecorded,
159
+ evictedCount: state.realtimeEvictedCount,
160
+ truncated: state.realtimeTruncated,
161
+ capacity: REALTIME_BUFFER_CAPACITY
162
+ },
163
+ generation: state.generation,
164
+ enabledInspectors: state.recordingMetadata.enabledInspectors
165
+ }
166
+ });
167
+ const ensureHttpRecord = (requestId, fallback) => {
168
+ const existing = state.httpRecords.get(requestId);
169
+ if (existing) {
170
+ return existing;
171
+ }
172
+ const record = {
173
+ requestId,
174
+ request: fallback?.request || {
175
+ url: "",
176
+ method: "GET",
177
+ headers: {}
178
+ },
179
+ resourceType: fallback?.resourceType || "Other",
180
+ initiator: fallback?.initiator || { type: "other" },
181
+ startTimeMs: fallback?.startTimeMs ?? Date.now(),
182
+ status: fallback?.status || "pending"
183
+ };
184
+ state.httpRecords.set(requestId, record);
185
+ state.httpOrder.push(requestId);
186
+ state.httpTotalRecorded += 1;
187
+ const evicted = trimMap(
188
+ state.httpOrder,
189
+ state.httpRecords,
190
+ HTTP_BUFFER_CAPACITY
191
+ );
192
+ if (evicted > 0) {
193
+ state.httpEvictedCount += evicted;
194
+ state.httpTruncated = true;
195
+ }
196
+ return record;
197
+ };
198
+ const ensureRealtimeRecord = (requestId, createRecord) => {
199
+ const existing = state.realtimeRecords.get(requestId);
200
+ if (existing) {
201
+ return existing;
202
+ }
203
+ const record = createRecord();
204
+ state.realtimeRecords.set(requestId, record);
205
+ state.realtimeOrder.push(requestId);
206
+ state.realtimeTotalRecorded += 1;
207
+ const evicted = trimMap(
208
+ state.realtimeOrder,
209
+ state.realtimeRecords,
210
+ REALTIME_BUFFER_CAPACITY
211
+ );
212
+ if (evicted > 0) {
213
+ state.realtimeEvictedCount += evicted;
214
+ state.realtimeTruncated = true;
215
+ }
216
+ return record;
217
+ };
218
+ const nextMessageId = (prefix) => {
219
+ const id = `${prefix}-${state.nextMessageId}`;
220
+ state.nextMessageId += 1;
221
+ return id;
222
+ };
223
+ return {
224
+ startRecording(metadata) {
225
+ state.isRecording = true;
226
+ state.startedAt = Date.now();
227
+ state.stoppedAt = void 0;
228
+ state.generation += 1;
229
+ state.httpOrder = [];
230
+ state.httpRecords.clear();
231
+ state.httpTotalRecorded = 0;
232
+ state.httpEvictedCount = 0;
233
+ state.httpTruncated = false;
234
+ state.realtimeOrder = [];
235
+ state.realtimeRecords.clear();
236
+ state.realtimeTotalRecorded = 0;
237
+ state.realtimeEvictedCount = 0;
238
+ state.realtimeTruncated = false;
239
+ state.recordingMetadata = {
240
+ enabledInspectors: {
241
+ ...state.recordingMetadata.enabledInspectors,
242
+ ...metadata?.enabledInspectors
243
+ }
244
+ };
245
+ return getStatus();
246
+ },
247
+ stopRecording() {
248
+ if (!state.isRecording) {
249
+ throw new Error("No active network recording for this plugin session");
250
+ }
251
+ state.isRecording = false;
252
+ state.stoppedAt = Date.now();
253
+ return getStatus();
254
+ },
255
+ getStatus,
256
+ getHttpRecord(requestId) {
257
+ return state.httpRecords.get(requestId) || null;
258
+ },
259
+ getRealtimeRecord(requestId) {
260
+ return state.realtimeRecords.get(requestId) || null;
261
+ },
262
+ listRequests(input) {
263
+ const limit = getLimit(input.limit);
264
+ const rows = state.httpOrder.map((requestId) => state.httpRecords.get(requestId)).filter((record) => !!record).reverse().map(createHttpSummary);
265
+ return {
266
+ ...getStatus(),
267
+ ...paginate(rows, `http-${state.generation}`, limit, input.cursor)
268
+ };
269
+ },
270
+ listRealtimeConnections(input) {
271
+ const limit = getLimit(input.limit);
272
+ const rows = state.realtimeOrder.map((requestId) => state.realtimeRecords.get(requestId)).filter((record) => !!record).reverse().map(getRealtimeSummary);
273
+ return {
274
+ ...getStatus(),
275
+ ...paginate(rows, `realtime-${state.generation}`, limit, input.cursor)
276
+ };
277
+ },
278
+ getRequestDetails(requestId) {
279
+ const record = state.httpRecords.get(requestId);
280
+ if (!record) {
281
+ throw new Error(`Unknown request "${requestId}"`);
282
+ }
283
+ return {
284
+ ...getStatus(),
285
+ request: {
286
+ requestId: record.requestId,
287
+ method: record.request.method,
288
+ url: record.request.url,
289
+ type: record.resourceType,
290
+ initiator: record.initiator,
291
+ startTimeMs: record.startTimeMs,
292
+ endTimeMs: record.endTimeMs ?? null,
293
+ durationMs: record.durationMs ?? null,
294
+ request: record.request,
295
+ response: record.response ?? null,
296
+ loadingFinished: record.status === "finished",
297
+ loadingFailed: record.status === "failed",
298
+ failureText: record.error ?? null,
299
+ canceled: record.canceled ?? false,
300
+ progress: record.progress ? {
301
+ loaded: record.progress.loaded,
302
+ total: record.progress.total,
303
+ lengthComputable: record.progress.lengthComputable
304
+ } : null,
305
+ ttfb: record.ttfb ?? null,
306
+ size: record.size ?? null
307
+ }
308
+ };
309
+ },
310
+ getRealtimeConnectionDetails(requestId) {
311
+ const record = state.realtimeRecords.get(requestId);
312
+ if (!record) {
313
+ throw new Error(`Unknown realtime connection "${requestId}"`);
314
+ }
315
+ return {
316
+ ...getStatus(),
317
+ connection: record.kind === "websocket" ? {
318
+ requestId: record.requestId,
319
+ kind: record.kind,
320
+ url: record.url,
321
+ socketId: record.socketId,
322
+ status: record.status,
323
+ startedAt: record.startedAt,
324
+ endedAt: record.endedAt ?? null,
325
+ durationMs: record.durationMs ?? null,
326
+ protocols: record.protocols ?? null,
327
+ options: record.options ?? [],
328
+ error: record.error ?? null,
329
+ closeCode: record.closeCode ?? null,
330
+ closeReason: record.closeReason ?? null,
331
+ messages: record.messages
332
+ } : {
333
+ requestId: record.requestId,
334
+ kind: record.kind,
335
+ status: record.status,
336
+ startedAt: record.startedAt,
337
+ endedAt: record.endedAt ?? null,
338
+ durationMs: record.durationMs ?? null,
339
+ request: record.request ?? null,
340
+ response: record.response ?? null,
341
+ initiator: record.initiator ?? null,
342
+ resourceType: record.resourceType ?? null,
343
+ error: record.error ?? null,
344
+ messages: record.messages
345
+ }
346
+ };
347
+ },
348
+ getRequestBody(requestId) {
349
+ const record = state.httpRecords.get(requestId);
350
+ if (!record) {
351
+ throw new Error(`Unknown request "${requestId}"`);
352
+ }
353
+ return serializeRequestBody(requestId, record.request.postData);
354
+ },
355
+ onRequestSent(event) {
356
+ if (!state.isRecording) {
357
+ return;
358
+ }
359
+ const record = ensureHttpRecord(event.requestId, {
360
+ request: event.request,
361
+ resourceType: event.type,
362
+ initiator: event.initiator,
363
+ startTimeMs: event.timestamp,
364
+ status: "pending"
365
+ });
366
+ record.request = event.request;
367
+ record.resourceType = event.type;
368
+ record.initiator = event.initiator;
369
+ record.startTimeMs = event.timestamp;
370
+ record.status = "pending";
371
+ record.response = void 0;
372
+ record.endTimeMs = void 0;
373
+ record.durationMs = void 0;
374
+ record.size = void 0;
375
+ record.ttfb = void 0;
376
+ record.error = void 0;
377
+ record.canceled = void 0;
378
+ record.progress = void 0;
379
+ },
380
+ onRequestProgress(event) {
381
+ if (!state.isRecording) {
382
+ return;
383
+ }
384
+ const record = ensureHttpRecord(event.requestId);
385
+ record.progress = event;
386
+ record.status = "loading";
387
+ },
388
+ onResponseReceived(event) {
389
+ if (!state.isRecording) {
390
+ return;
391
+ }
392
+ const record = ensureHttpRecord(event.requestId);
393
+ record.response = event.response;
394
+ record.status = "loading";
395
+ },
396
+ onRequestCompleted(event) {
397
+ if (!state.isRecording) {
398
+ return;
399
+ }
400
+ const record = ensureHttpRecord(event.requestId);
401
+ record.status = "finished";
402
+ record.endTimeMs = event.timestamp;
403
+ record.durationMs = event.duration;
404
+ record.size = event.size;
405
+ record.ttfb = event.ttfb;
406
+ },
407
+ onRequestFailed(event) {
408
+ if (!state.isRecording) {
409
+ return;
410
+ }
411
+ const record = ensureHttpRecord(event.requestId);
412
+ record.status = "failed";
413
+ record.endTimeMs = event.timestamp;
414
+ record.error = event.error;
415
+ record.canceled = event.canceled;
416
+ },
417
+ onWebSocketConnect(event) {
418
+ if (!state.isRecording) {
419
+ return;
420
+ }
421
+ ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
422
+ requestId: `ws-${event.socketId}`,
423
+ kind: "websocket",
424
+ url: event.url,
425
+ socketId: event.socketId,
426
+ status: "connecting",
427
+ startedAt: event.timestamp,
428
+ protocols: event.protocols,
429
+ options: event.options,
430
+ messages: []
431
+ }));
432
+ },
433
+ onWebSocketOpen(event) {
434
+ if (!state.isRecording) {
435
+ return;
436
+ }
437
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
438
+ requestId: `ws-${event.socketId}`,
439
+ kind: "websocket",
440
+ url: event.url,
441
+ socketId: event.socketId,
442
+ status: "connecting",
443
+ startedAt: event.timestamp,
444
+ messages: []
445
+ }));
446
+ record.status = "open";
447
+ },
448
+ onWebSocketClose(event) {
449
+ if (!state.isRecording) {
450
+ return;
451
+ }
452
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
453
+ requestId: `ws-${event.socketId}`,
454
+ kind: "websocket",
455
+ url: event.url,
456
+ socketId: event.socketId,
457
+ status: "connecting",
458
+ startedAt: event.timestamp,
459
+ messages: []
460
+ }));
461
+ record.status = "closed";
462
+ record.endedAt = event.timestamp;
463
+ record.durationMs = event.timestamp - record.startedAt;
464
+ record.closeCode = event.code;
465
+ record.closeReason = event.reason;
466
+ },
467
+ onWebSocketMessageSent(event) {
468
+ if (!state.isRecording) {
469
+ return;
470
+ }
471
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
472
+ requestId: `ws-${event.socketId}`,
473
+ kind: "websocket",
474
+ url: event.url,
475
+ socketId: event.socketId,
476
+ status: "connecting",
477
+ startedAt: event.timestamp,
478
+ messages: []
479
+ }));
480
+ const message = {
481
+ id: nextMessageId(record.requestId),
482
+ direction: "sent",
483
+ data: event.data,
484
+ messageType: event.messageType,
485
+ timestamp: event.timestamp
486
+ };
487
+ record.messages = [...record.messages, message].slice(
488
+ -32
489
+ );
490
+ },
491
+ onWebSocketMessageReceived(event) {
492
+ if (!state.isRecording) {
493
+ return;
494
+ }
495
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
496
+ requestId: `ws-${event.socketId}`,
497
+ kind: "websocket",
498
+ url: event.url,
499
+ socketId: event.socketId,
500
+ status: "connecting",
501
+ startedAt: event.timestamp,
502
+ messages: []
503
+ }));
504
+ const message = {
505
+ id: nextMessageId(record.requestId),
506
+ direction: "received",
507
+ data: event.data,
508
+ messageType: event.messageType,
509
+ timestamp: event.timestamp
510
+ };
511
+ record.messages = [...record.messages, message].slice(
512
+ -32
513
+ );
514
+ },
515
+ onWebSocketError(event) {
516
+ if (!state.isRecording) {
517
+ return;
518
+ }
519
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
520
+ requestId: `ws-${event.socketId}`,
521
+ kind: "websocket",
522
+ url: event.url,
523
+ socketId: event.socketId,
524
+ status: "connecting",
525
+ startedAt: event.timestamp,
526
+ messages: []
527
+ }));
528
+ record.status = "error";
529
+ record.error = event.error;
530
+ },
531
+ onWebSocketConnectionStatusChanged(event) {
532
+ if (!state.isRecording) {
533
+ return;
534
+ }
535
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
536
+ requestId: `ws-${event.socketId}`,
537
+ kind: "websocket",
538
+ url: event.url,
539
+ socketId: event.socketId,
540
+ status: "connecting",
541
+ startedAt: event.timestamp,
542
+ messages: []
543
+ }));
544
+ record.status = event.status;
545
+ },
546
+ onSSEOpen(event) {
547
+ if (!state.isRecording) {
548
+ return;
549
+ }
550
+ const httpRecord = state.httpRecords.get(event.requestId);
551
+ ensureRealtimeRecord(event.requestId, () => ({
552
+ requestId: event.requestId,
553
+ kind: "sse",
554
+ status: "open",
555
+ startedAt: httpRecord?.startTimeMs ?? event.timestamp,
556
+ request: httpRecord?.request,
557
+ response: event.response,
558
+ initiator: httpRecord?.initiator,
559
+ resourceType: httpRecord?.resourceType,
560
+ messages: []
561
+ }));
562
+ },
563
+ onSSEMessage(event) {
564
+ if (!state.isRecording) {
565
+ return;
566
+ }
567
+ const record = ensureRealtimeRecord(event.requestId, () => ({
568
+ requestId: event.requestId,
569
+ kind: "sse",
570
+ status: "connecting",
571
+ startedAt: event.timestamp,
572
+ messages: []
573
+ }));
574
+ record.messages = [
575
+ ...record.messages,
576
+ {
577
+ id: nextMessageId(record.requestId),
578
+ type: event.payload.type,
579
+ data: event.payload.data,
580
+ timestamp: event.timestamp
581
+ }
582
+ ].slice(-32);
583
+ },
584
+ onSSEError(event) {
585
+ if (!state.isRecording) {
586
+ return;
587
+ }
588
+ const record = ensureRealtimeRecord(event.requestId, () => ({
589
+ requestId: event.requestId,
590
+ kind: "sse",
591
+ status: "connecting",
592
+ startedAt: event.timestamp,
593
+ messages: []
594
+ }));
595
+ record.status = "error";
596
+ record.error = event.error.message;
597
+ },
598
+ onSSEClose(event) {
599
+ if (!state.isRecording) {
600
+ return;
601
+ }
602
+ const record = ensureRealtimeRecord(event.requestId, () => ({
603
+ requestId: event.requestId,
604
+ kind: "sse",
605
+ status: "connecting",
606
+ startedAt: event.timestamp,
607
+ messages: []
608
+ }));
609
+ record.status = "closed";
610
+ record.endedAt = event.timestamp;
611
+ record.durationMs = event.timestamp - record.startedAt;
612
+ }
613
+ };
614
+ };
615
+ const getNetworkActivityAgentState = /* @__PURE__ */ (() => {
616
+ let instance = null;
617
+ return () => {
618
+ if (!instance) {
619
+ instance = createNetworkActivityAgentState();
620
+ }
621
+ return instance;
622
+ };
623
+ })();
624
+ const startRecordingTool = {
625
+ name: "startRecording",
626
+ description: "Start recording network activity in the fallback network activity plugin.",
627
+ inputSchema: {
628
+ type: "object",
629
+ properties: {}
630
+ }
631
+ };
632
+ const stopRecordingTool = {
633
+ name: "stopRecording",
634
+ description: "Stop recording network activity without clearing the captured plugin buffer.",
635
+ inputSchema: {
636
+ type: "object",
637
+ properties: {}
638
+ }
639
+ };
640
+ const getRecordingStatusTool = {
641
+ name: "getRecordingStatus",
642
+ description: "Return network activity plugin recording state and buffer metadata.",
643
+ inputSchema: {
644
+ type: "object",
645
+ properties: {}
646
+ }
647
+ };
648
+ const listRequestsTool = {
649
+ name: "listRequests",
650
+ description: "List captured HTTP request summaries with cursor pagination from the fallback plugin.",
651
+ inputSchema: {
652
+ type: "object",
653
+ properties: {
654
+ limit: {
655
+ type: "number",
656
+ description: "Maximum number of requests to return. Defaults to 20."
657
+ },
658
+ cursor: {
659
+ type: "string",
660
+ description: "Opaque pagination cursor from a previous listRequests call."
661
+ }
662
+ }
663
+ }
664
+ };
665
+ const getRequestDetailsTool = {
666
+ name: "getRequestDetails",
667
+ description: "Return detailed metadata for a captured HTTP request without fetching response body.",
668
+ inputSchema: {
669
+ type: "object",
670
+ properties: {
671
+ requestId: {
672
+ type: "string",
673
+ description: "Captured plugin request ID to inspect."
674
+ }
675
+ },
676
+ required: ["requestId"]
677
+ }
678
+ };
679
+ const getRequestBodyTool = {
680
+ name: "getRequestBody",
681
+ description: "Return the captured request body for a plugin-recorded HTTP request when available.",
682
+ inputSchema: {
683
+ type: "object",
684
+ properties: {
685
+ requestId: {
686
+ type: "string",
687
+ description: "Captured plugin request ID to inspect."
688
+ }
689
+ },
690
+ required: ["requestId"]
691
+ }
692
+ };
693
+ const getResponseBodyTool = {
694
+ name: "getResponseBody",
695
+ description: "Return the captured response body for a plugin-recorded HTTP request when available.",
696
+ inputSchema: {
697
+ type: "object",
698
+ properties: {
699
+ requestId: {
700
+ type: "string",
701
+ description: "Captured plugin request ID to inspect."
702
+ }
703
+ },
704
+ required: ["requestId"]
705
+ }
706
+ };
707
+ const listRealtimeConnectionsTool = {
708
+ name: "listRealtimeConnections",
709
+ description: "List captured WebSocket and SSE connections with cursor pagination.",
710
+ inputSchema: {
711
+ type: "object",
712
+ properties: {
713
+ limit: {
714
+ type: "number",
715
+ description: "Maximum number of realtime connections to return. Defaults to 20."
716
+ },
717
+ cursor: {
718
+ type: "string",
719
+ description: "Opaque pagination cursor from a previous listRealtimeConnections call."
720
+ }
721
+ }
722
+ }
723
+ };
724
+ const getRealtimeConnectionDetailsTool = {
725
+ name: "getRealtimeConnectionDetails",
726
+ description: "Return details for a captured WebSocket or SSE connection, including recent messages.",
727
+ inputSchema: {
728
+ type: "object",
729
+ properties: {
730
+ requestId: {
731
+ type: "string",
732
+ description: "Captured realtime request ID to inspect."
733
+ }
734
+ },
735
+ required: ["requestId"]
736
+ }
737
+ };
738
+ const pluginId = "@rozenite/network-activity-plugin";
739
+ const useNetworkActivityAgentTools = ({
740
+ client,
741
+ networkInspector,
742
+ enabledInspectors
743
+ }) => {
744
+ const state = getNetworkActivityAgentState();
745
+ useEffect(() => {
746
+ const unsubscribe = [
747
+ networkInspector.http.on(
748
+ "request-sent",
749
+ (event) => state.onRequestSent(event)
750
+ ),
751
+ networkInspector.http.on(
752
+ "request-progress",
753
+ (event) => state.onRequestProgress(event)
754
+ ),
755
+ networkInspector.http.on(
756
+ "response-received",
757
+ (event) => state.onResponseReceived(event)
758
+ ),
759
+ networkInspector.http.on(
760
+ "request-completed",
761
+ (event) => state.onRequestCompleted(event)
762
+ ),
763
+ networkInspector.http.on(
764
+ "request-failed",
765
+ (event) => state.onRequestFailed(event)
766
+ ),
767
+ networkInspector.websocket.on(
768
+ "websocket-connect",
769
+ (event) => state.onWebSocketConnect(event)
770
+ ),
771
+ networkInspector.websocket.on(
772
+ "websocket-open",
773
+ (event) => state.onWebSocketOpen(event)
774
+ ),
775
+ networkInspector.websocket.on(
776
+ "websocket-close",
777
+ (event) => state.onWebSocketClose(event)
778
+ ),
779
+ networkInspector.websocket.on(
780
+ "websocket-message-sent",
781
+ (event) => state.onWebSocketMessageSent(event)
782
+ ),
783
+ networkInspector.websocket.on(
784
+ "websocket-message-received",
785
+ (event) => state.onWebSocketMessageReceived(event)
786
+ ),
787
+ networkInspector.websocket.on(
788
+ "websocket-error",
789
+ (event) => state.onWebSocketError(event)
790
+ ),
791
+ networkInspector.websocket.on(
792
+ "websocket-connection-status-changed",
793
+ (event) => state.onWebSocketConnectionStatusChanged(event)
794
+ ),
795
+ networkInspector.sse.on("sse-open", (event) => state.onSSEOpen(event)),
796
+ networkInspector.sse.on("sse-message", (event) => state.onSSEMessage(event)),
797
+ networkInspector.sse.on("sse-error", (event) => state.onSSEError(event)),
798
+ networkInspector.sse.on("sse-close", (event) => state.onSSEClose(event))
799
+ ];
800
+ return () => {
801
+ unsubscribe.forEach((remove) => remove());
802
+ };
803
+ }, [networkInspector, state]);
804
+ useEffect(() => {
805
+ if (!client) {
806
+ return;
807
+ }
808
+ const subscriptions = [
809
+ client.onMessage("network-enable", () => {
810
+ state.startRecording({ enabledInspectors });
811
+ }),
812
+ client.onMessage("network-disable", () => {
813
+ if (state.getStatus().recording.isRecording) {
814
+ state.stopRecording();
815
+ }
816
+ })
817
+ ];
818
+ return () => {
819
+ subscriptions.forEach((subscription) => subscription.remove());
820
+ };
821
+ }, [client, enabledInspectors, state]);
822
+ useRozenitePluginAgentTool({
823
+ pluginId,
824
+ tool: startRecordingTool,
825
+ handler: () => {
826
+ networkInspector.http.getNetworkRequestsRegistry().clear();
827
+ const result = state.startRecording({ enabledInspectors });
828
+ networkInspector.enable(enabledInspectors);
829
+ return {
830
+ started: true,
831
+ ...result
832
+ };
833
+ }
834
+ });
835
+ useRozenitePluginAgentTool({
836
+ pluginId,
837
+ tool: stopRecordingTool,
838
+ handler: () => {
839
+ const result = state.stopRecording();
840
+ networkInspector.disable();
841
+ return {
842
+ stopped: true,
843
+ ...result
844
+ };
845
+ }
846
+ });
847
+ useRozenitePluginAgentTool({
848
+ pluginId,
849
+ tool: getRecordingStatusTool,
850
+ handler: () => state.getStatus()
851
+ });
852
+ useRozenitePluginAgentTool({
853
+ pluginId,
854
+ tool: listRequestsTool,
855
+ handler: (input = {}) => state.listRequests(input)
856
+ });
857
+ useRozenitePluginAgentTool({
858
+ pluginId,
859
+ tool: getRequestDetailsTool,
860
+ handler: ({ requestId }) => state.getRequestDetails(requestId)
861
+ });
862
+ useRozenitePluginAgentTool({
863
+ pluginId,
864
+ tool: getRequestBodyTool,
865
+ handler: ({ requestId }) => state.getRequestBody(requestId)
866
+ });
867
+ useRozenitePluginAgentTool({
868
+ pluginId,
869
+ tool: getResponseBodyTool,
870
+ handler: async ({ requestId }) => {
871
+ const record = state.getHttpRecord(requestId);
872
+ if (!record) {
873
+ throw new Error(`Unknown request "${requestId}"`);
874
+ }
875
+ if (record.status === "failed") {
876
+ return {
877
+ requestId,
878
+ available: false,
879
+ reason: "Response body is unavailable because the request failed."
880
+ };
881
+ }
882
+ if (record.status !== "finished") {
883
+ return {
884
+ requestId,
885
+ available: false,
886
+ reason: "Response body is unavailable until the request finishes loading."
887
+ };
888
+ }
889
+ const request = networkInspector.http.getNetworkRequestsRegistry().getEntry(requestId);
890
+ if (!request) {
891
+ return {
892
+ requestId,
893
+ available: false,
894
+ reason: "Response body is unavailable because the request object is no longer in the plugin registry."
895
+ };
896
+ }
897
+ const body = await getResponseBody(request);
898
+ if (body === null) {
899
+ return {
900
+ requestId,
901
+ available: false,
902
+ reason: "The plugin could not extract a text response body for this request."
903
+ };
904
+ }
905
+ return {
906
+ requestId,
907
+ available: true,
908
+ body,
909
+ base64Encoded: false,
910
+ decoded: false,
911
+ mimeType: record.response?.contentType
912
+ };
913
+ }
914
+ });
915
+ useRozenitePluginAgentTool({
916
+ pluginId,
917
+ tool: listRealtimeConnectionsTool,
918
+ handler: (input = {}) => state.listRealtimeConnections(input)
919
+ });
920
+ useRozenitePluginAgentTool({
921
+ pluginId,
922
+ tool: getRealtimeConnectionDetailsTool,
923
+ handler: ({ requestId }) => state.getRealtimeConnectionDetails(requestId)
924
+ });
925
+ };
926
+ const overridesRegistry = getOverridesRegistry();
927
+ const useHttpInspector = (client, httpInspector, isEnabled, isRecordingEnabled) => {
928
+ useEffect(() => {
929
+ if (!client || !isEnabled) {
930
+ return;
931
+ }
932
+ const networkRequestsRegistry = httpInspector.getNetworkRequestsRegistry();
933
+ const subscriptions = [
934
+ client.onMessage("network-enable", () => {
935
+ httpInspector.enable();
936
+ }),
937
+ client.onMessage("network-disable", () => {
938
+ httpInspector.disable();
939
+ }),
940
+ client.onMessage("set-overrides", (data) => {
941
+ overridesRegistry.setOverrides(data.overrides);
942
+ }),
943
+ client.onMessage("get-response-body", async ({ requestId }) => {
944
+ const request = networkRequestsRegistry.getEntry(requestId);
945
+ if (!request) {
946
+ return;
947
+ }
948
+ const body = await getResponseBody(request);
949
+ client.send("response-body", {
950
+ requestId,
951
+ body
952
+ });
953
+ })
954
+ ];
955
+ if (isRecordingEnabled) {
956
+ httpInspector.enable();
957
+ }
958
+ return () => {
959
+ subscriptions.forEach((subscription) => subscription.remove());
960
+ httpInspector.dispose();
961
+ };
962
+ }, [client, httpInspector, isEnabled, isRecordingEnabled]);
963
+ };
964
+ const useWebSocketInspector = (client, websocketInspector, isEnabled, isRecordingEnabled) => {
965
+ useEffect(() => {
966
+ if (!client || !isEnabled) {
967
+ return;
968
+ }
969
+ const subscriptions = [
970
+ client.onMessage("network-enable", () => {
971
+ websocketInspector.enable();
972
+ }),
973
+ client.onMessage("network-disable", () => {
974
+ websocketInspector.disable();
975
+ })
976
+ ];
977
+ if (isRecordingEnabled) {
978
+ websocketInspector.enable();
979
+ }
980
+ return () => {
981
+ subscriptions.forEach((subscription) => subscription.remove());
982
+ websocketInspector.dispose();
983
+ };
984
+ }, [client, websocketInspector, isEnabled, isRecordingEnabled]);
985
+ };
986
+ const useSSEInspector = (client, sseInspector, isEnabled, isRecordingEnabled) => {
987
+ useEffect(() => {
988
+ if (!client || !isEnabled) {
989
+ return;
990
+ }
991
+ const subscriptions = [
992
+ client.onMessage("network-enable", () => {
993
+ sseInspector.enable();
994
+ }),
995
+ client.onMessage("network-disable", () => {
996
+ sseInspector.disable();
997
+ })
998
+ ];
999
+ if (isRecordingEnabled) {
1000
+ sseInspector.enable();
1001
+ }
1002
+ return () => {
1003
+ subscriptions.forEach((subscription) => subscription.remove());
1004
+ sseInspector.dispose();
1005
+ };
1006
+ }, [client, sseInspector, isEnabled, isRecordingEnabled]);
1007
+ };
1008
+ const inspectorsConfig = createNetworkInspectorsConfiguration();
1009
+ const useNetworkActivityDevTools = (config = DEFAULT_CONFIG) => {
1010
+ const isRecordingEnabledRef = useRef(false);
1011
+ const client = useRozeniteDevToolsClient({
1012
+ pluginId: "@rozenite/network-activity-plugin"
1013
+ });
1014
+ const isHttpInspectorEnabled = config.inspectors?.http ?? true;
1015
+ const isWebSocketInspectorEnabled = config.inspectors?.websocket ?? true;
1016
+ const isSSEInspectorEnabled = config.inspectors?.sse ?? true;
1017
+ const showUrlAsName = config.clientUISettings?.showUrlAsName;
1018
+ const { eventsListener, networkInspector } = inspectorsConfig;
1019
+ useNetworkActivityAgentTools({
1020
+ client,
1021
+ networkInspector,
1022
+ enabledInspectors: {
1023
+ http: isHttpInspectorEnabled,
1024
+ websocket: isWebSocketInspectorEnabled,
1025
+ sse: isSSEInspectorEnabled
1026
+ }
1027
+ });
1028
+ useEffect(() => {
1029
+ if (!client) {
1030
+ return;
1031
+ }
1032
+ validateConfig(config);
1033
+ }, [config]);
1034
+ useEffect(() => {
1035
+ if (!client) {
1036
+ return;
1037
+ }
1038
+ const sendClientUISettings = () => {
1039
+ client.send("client-ui-settings", {
1040
+ settings: {
1041
+ showUrlAsName: showUrlAsName ?? DEFAULT_CONFIG.clientUISettings?.showUrlAsName
1042
+ }
1043
+ });
1044
+ };
1045
+ const subscriptions = [
1046
+ client.onMessage("network-enable", () => {
1047
+ isRecordingEnabledRef.current = true;
1048
+ eventsListener.connect(client.send, (message) => {
1049
+ const type = message.type;
1050
+ if (isHttpEvent(type)) {
1051
+ return isHttpInspectorEnabled;
1052
+ }
1053
+ if (isWebSocketEvent(type)) {
1054
+ return isWebSocketInspectorEnabled;
1055
+ }
1056
+ if (isSSEEvent(type)) {
1057
+ return isSSEInspectorEnabled;
1058
+ }
1059
+ return true;
1060
+ });
1061
+ }),
1062
+ client.onMessage("network-disable", () => {
1063
+ isRecordingEnabledRef.current = false;
1064
+ }),
1065
+ client.onMessage("get-client-ui-settings", () => {
1066
+ sendClientUISettings();
1067
+ })
1068
+ ];
1069
+ sendClientUISettings();
1070
+ return () => {
1071
+ subscriptions.forEach((subscription) => subscription.remove());
1072
+ };
1073
+ }, [
1074
+ client,
1075
+ showUrlAsName,
1076
+ isHttpInspectorEnabled,
1077
+ isWebSocketInspectorEnabled,
1078
+ isSSEInspectorEnabled
1079
+ ]);
1080
+ useHttpInspector(
1081
+ client,
1082
+ networkInspector.http,
1083
+ isHttpInspectorEnabled,
1084
+ isRecordingEnabledRef.current
1085
+ );
1086
+ useWebSocketInspector(
1087
+ client,
1088
+ networkInspector.websocket,
1089
+ isWebSocketInspectorEnabled,
1090
+ isRecordingEnabledRef.current
1091
+ );
1092
+ useSSEInspector(
1093
+ client,
1094
+ networkInspector.sse,
1095
+ isSSEInspectorEnabled,
1096
+ isRecordingEnabledRef.current
1097
+ );
1098
+ return client;
1099
+ };
1100
+ export {
1101
+ useNetworkActivityDevTools
1102
+ };