@rozenite/network-activity-plugin 1.9.0 → 1.11.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 (84) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/devtools/App.html +2 -2
  3. package/dist/devtools/assets/{App-hSoryVpJ.js → App-CEESZAW_.js} +7520 -937
  4. package/dist/devtools/assets/{App-m6xge0az.css → App-xppYUJvX.css} +246 -2
  5. package/dist/react-native/chunks/boot-recording.cjs +138 -14
  6. package/dist/react-native/chunks/boot-recording.js +138 -14
  7. package/dist/react-native/chunks/get-nitro-module.cjs +4 -1
  8. package/dist/react-native/chunks/get-nitro-module.js +4 -1
  9. package/dist/react-native/chunks/useNetworkActivityDevTools.require.cjs +20 -1
  10. package/dist/react-native/chunks/useNetworkActivityDevTools.require.js +20 -1
  11. package/dist/react-native/index.d.ts +37 -1
  12. package/dist/rozenite.json +1 -1
  13. package/dist/sdk/index.d.ts +37 -1
  14. package/package.json +12 -7
  15. package/src/react-native/agent/use-network-activity-agent-tools.ts +22 -4
  16. package/src/react-native/http/__tests__/http-utils.test.ts +228 -0
  17. package/src/react-native/http/http-utils.ts +208 -25
  18. package/src/react-native/network-inspector.ts +2 -2
  19. package/src/react-native/nitro-fetch/get-nitro-module.ts +5 -1
  20. package/src/react-native/nitro-fetch/nitro-network-inspector.ts +8 -2
  21. package/src/shared/http-events.ts +40 -1
  22. package/src/ui/components/CodeBlock.tsx +45 -1
  23. package/src/ui/components/FilterBar.tsx +337 -61
  24. package/src/ui/components/HexView.tsx +54 -0
  25. package/src/ui/components/MetadataCard.tsx +95 -0
  26. package/src/ui/components/NetworkTimeline.tsx +422 -0
  27. package/src/ui/components/RequestList.tsx +19 -40
  28. package/src/ui/components/SidePanel.tsx +42 -1
  29. package/src/ui/components/Toolbar.tsx +13 -1
  30. package/src/ui/components/ViewToggle.tsx +44 -0
  31. package/src/ui/components/XmlTree.tsx +160 -0
  32. package/src/ui/components/__tests__/CodeBlock.test.tsx +89 -0
  33. package/src/ui/components/__tests__/HexView.test.tsx +41 -0
  34. package/src/ui/components/__tests__/MetadataCard.test.tsx +107 -0
  35. package/src/ui/components/__tests__/ViewToggle.test.tsx +80 -0
  36. package/src/ui/components/__tests__/XmlTree.test.tsx +149 -0
  37. package/src/ui/hooks/useNetworkActivitySessionExport.ts +39 -0
  38. package/src/ui/response-renderers/__tests__/binary-too-large.test.tsx +56 -0
  39. package/src/ui/response-renderers/__tests__/binary.test.tsx +96 -0
  40. package/src/ui/response-renderers/__tests__/dispatch.test.ts +124 -0
  41. package/src/ui/response-renderers/__tests__/html.test.tsx +101 -0
  42. package/src/ui/response-renderers/__tests__/image.test.tsx +73 -0
  43. package/src/ui/response-renderers/__tests__/json.test.tsx +95 -0
  44. package/src/ui/response-renderers/__tests__/svg.test.tsx +46 -0
  45. package/src/ui/response-renderers/__tests__/xml.test.tsx +100 -0
  46. package/src/ui/response-renderers/binary-too-large.tsx +36 -0
  47. package/src/ui/response-renderers/binary.tsx +31 -0
  48. package/src/ui/response-renderers/empty.tsx +14 -0
  49. package/src/ui/response-renderers/html.tsx +36 -0
  50. package/src/ui/response-renderers/image.tsx +37 -0
  51. package/src/ui/response-renderers/index.ts +55 -0
  52. package/src/ui/response-renderers/json.tsx +40 -0
  53. package/src/ui/response-renderers/svg.tsx +27 -0
  54. package/src/ui/response-renderers/text-fallback.tsx +14 -0
  55. package/src/ui/response-renderers/types.ts +38 -0
  56. package/src/ui/response-renderers/unknown.tsx +18 -0
  57. package/src/ui/response-renderers/xml.tsx +46 -0
  58. package/src/ui/state/__tests__/store.test.ts +77 -0
  59. package/src/ui/state/derived.ts +14 -0
  60. package/src/ui/state/filter.ts +49 -0
  61. package/src/ui/state/hooks.ts +2 -2
  62. package/src/ui/state/model.ts +7 -1
  63. package/src/ui/state/store.ts +63 -4
  64. package/src/ui/tabs/InitiatorTab.tsx +230 -0
  65. package/src/ui/tabs/ResponseTab.tsx +80 -97
  66. package/src/ui/tabs/__tests__/ResponseTab.test.tsx +102 -0
  67. package/src/ui/utils/__tests__/download.test.ts +115 -0
  68. package/src/ui/utils/__tests__/hex.test.ts +84 -0
  69. package/src/ui/utils/__tests__/requestFilters.test.ts +32 -0
  70. package/src/ui/utils/__tests__/sessionExport.test.ts +174 -0
  71. package/src/ui/utils/__tests__/symbolication.test.ts +207 -0
  72. package/src/ui/utils/__tests__/timelineModel.test.ts +170 -0
  73. package/src/ui/utils/download.ts +161 -0
  74. package/src/ui/utils/hex.ts +59 -0
  75. package/src/ui/utils/initiator.ts +136 -0
  76. package/src/ui/utils/requestFilters.ts +183 -0
  77. package/src/ui/utils/sessionExport.ts +185 -0
  78. package/src/ui/utils/symbolication.ts +248 -0
  79. package/src/ui/utils/timelineModel.ts +352 -0
  80. package/src/ui/views/InspectorView.tsx +43 -8
  81. package/src/utils/__tests__/getContentTypeMimeType.test.ts +34 -0
  82. package/src/utils/getContentTypeMimeType.ts +14 -0
  83. package/vite.config.ts +5 -1
  84. package/vitest.setup.ts +31 -0
@@ -210,6 +210,13 @@ function isJsonContentType(contentType) {
210
210
  const mimeType = normalizeContentType(contentType);
211
211
  return mimeType === "application/json" || mimeType.endsWith("+json");
212
212
  }
213
+ function isXmlContentType(contentType) {
214
+ if (!contentType) {
215
+ return false;
216
+ }
217
+ const mimeType = normalizeContentType(contentType);
218
+ return mimeType === "application/xml" || mimeType === "text/xml" || mimeType.endsWith("+xml");
219
+ }
213
220
  const isBlob = (value) => value instanceof Blob;
214
221
  const isArrayBuffer = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
215
222
  const isFormData = (value) => value instanceof FormData;
@@ -324,6 +331,30 @@ const getResponseSize = (request) => {
324
331
  return null;
325
332
  }
326
333
  };
334
+ const BINARY_CAPTURE_SIZE_CAP = 5 * 1024 * 1024;
335
+ const readBlobAsText = (blob) => new Promise((resolve) => {
336
+ const reader = new FileReader();
337
+ reader.onload = () => resolve(reader.result);
338
+ reader.readAsText(blob);
339
+ });
340
+ const readBlobAsBase64 = (blob) => new Promise((resolve) => {
341
+ const reader = new FileReader();
342
+ reader.onload = () => {
343
+ const dataUrl = reader.result;
344
+ resolve(dataUrl.substring(dataUrl.indexOf(",") + 1));
345
+ };
346
+ reader.readAsDataURL(blob);
347
+ });
348
+ const ARRAY_BUFFER_BASE64_CHUNK = 32768;
349
+ const arrayBufferToBase64 = (buffer) => {
350
+ const bytes = new Uint8Array(buffer);
351
+ let binary = "";
352
+ for (let i = 0; i < bytes.length; i += ARRAY_BUFFER_BASE64_CHUNK) {
353
+ const chunk = bytes.subarray(i, i + ARRAY_BUFFER_BASE64_CHUNK);
354
+ binary += String.fromCharCode.apply(null, chunk);
355
+ }
356
+ return btoa(binary);
357
+ };
327
358
  const getResponseBody = async (request) => {
328
359
  const responseType = request.responseType;
329
360
  if (responseType === "" || responseType === "text") {
@@ -331,35 +362,128 @@ const getResponseBody = async (request) => {
331
362
  }
332
363
  if (responseType === "blob") {
333
364
  const contentType = request.getResponseHeader("Content-Type") || "";
334
- if (contentType.startsWith("text/") || isJsonContentType(contentType)) {
335
- return new Promise((resolve) => {
336
- const reader = new FileReader();
337
- reader.onload = () => {
338
- resolve(reader.result);
339
- };
340
- reader.readAsText(request.response);
341
- });
365
+ if (contentType.startsWith("text/") || isJsonContentType(contentType) || isXmlContentType(contentType)) {
366
+ return readBlobAsText(request.response);
367
+ }
368
+ const blob = request.response;
369
+ if (blob.size > BINARY_CAPTURE_SIZE_CAP) {
370
+ return { kind: "binary-too-large", size: blob.size };
371
+ }
372
+ return { kind: "binary", base64: await readBlobAsBase64(blob) };
373
+ }
374
+ if (responseType === "arraybuffer") {
375
+ const buffer = request.response;
376
+ if (!buffer || buffer.byteLength === 0) {
377
+ return null;
342
378
  }
379
+ if (buffer.byteLength > BINARY_CAPTURE_SIZE_CAP) {
380
+ return { kind: "binary-too-large", size: buffer.byteLength };
381
+ }
382
+ return { kind: "binary", base64: arrayBufferToBase64(buffer) };
343
383
  }
344
384
  if (responseType === "json") {
345
385
  return safeStringify(request.response);
346
386
  }
347
387
  return null;
348
388
  };
389
+ const STACK_PREVIEW_FRAME_LIMIT = 8;
390
+ const INITIATOR_STACK_FRAME_OFFSET = 3;
391
+ const parseStackLocation = (location) => {
392
+ const match = location.match(/^(.*):(\d+):(\d+)$/);
393
+ if (!match) {
394
+ return null;
395
+ }
396
+ return {
397
+ url: match[1],
398
+ lineNumber: Number.parseInt(match[2], 10),
399
+ columnNumber: Number.parseInt(match[3], 10)
400
+ };
401
+ };
402
+ const normalizeFunctionName = (functionName) => {
403
+ const trimmedFunctionName = functionName?.trim();
404
+ return trimmedFunctionName && trimmedFunctionName !== "<anonymous>" && trimmedFunctionName !== "anonymous" && trimmedFunctionName !== "<unknown>" ? trimmedFunctionName : void 0;
405
+ };
406
+ const parseStackFrame = (line) => {
407
+ const trimmedLine = line.trim();
408
+ if (!trimmedLine) {
409
+ return null;
410
+ }
411
+ let functionName;
412
+ let location;
413
+ const v8FunctionFrame = trimmedLine.match(/^at\s+(.*?)\s+\((.*)\)$/);
414
+ if (v8FunctionFrame) {
415
+ functionName = v8FunctionFrame[1];
416
+ location = v8FunctionFrame[2];
417
+ } else {
418
+ const v8LocationFrame = trimmedLine.match(/^at\s+(.*)$/);
419
+ const jscFrame = trimmedLine.match(/^(.*?)@(.*)$/);
420
+ if (v8LocationFrame) {
421
+ location = v8LocationFrame[1];
422
+ } else if (jscFrame) {
423
+ functionName = jscFrame[1];
424
+ location = jscFrame[2];
425
+ }
426
+ }
427
+ if (!location) {
428
+ return null;
429
+ }
430
+ const parsedLocation = parseStackLocation(location);
431
+ if (!parsedLocation) {
432
+ return null;
433
+ }
434
+ return {
435
+ functionName: normalizeFunctionName(functionName),
436
+ ...parsedLocation
437
+ };
438
+ };
439
+ const toGeneratedStackFrame = (frame) => ({
440
+ functionName: frame.functionName,
441
+ generatedUrl: frame.url,
442
+ generatedLineNumber: frame.lineNumber,
443
+ generatedColumnNumber: frame.columnNumber
444
+ });
445
+ const getGeneratedFrameLocation = (frame) => ({
446
+ url: frame.generatedUrl ?? frame.url,
447
+ lineNumber: frame.generatedLineNumber ?? frame.lineNumber,
448
+ columnNumber: frame.generatedColumnNumber ?? frame.columnNumber
449
+ });
450
+ const canSymbolicateStack = (stack) => stack?.some(
451
+ (frame) => getGeneratedFrameLocation(frame).url?.startsWith("http")
452
+ ) ?? false;
453
+ const getStackPreview = (frames) => {
454
+ const callerFrames = frames.slice(INITIATOR_STACK_FRAME_OFFSET);
455
+ return (callerFrames.length > 0 ? callerFrames : frames).slice(
456
+ 0,
457
+ STACK_PREVIEW_FRAME_LIMIT
458
+ );
459
+ };
349
460
  const getInitiatorFromStack = () => {
350
461
  try {
351
462
  const stack = new Error().stack;
352
463
  if (!stack) {
353
464
  return { type: "other" };
354
465
  }
355
- const line = stack.split("\n")[9];
356
- const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
357
- if (match) {
466
+ const parsedFrames = stack.split("\n").map(parseStackFrame).filter((frame) => frame !== null);
467
+ const stackPreview = getStackPreview(parsedFrames);
468
+ const initiatorFrame = stackPreview[0];
469
+ const generatedStackPreview = stackPreview.map(toGeneratedStackFrame);
470
+ if (initiatorFrame?.url) {
358
471
  return {
359
472
  type: "script",
360
- url: match[2],
361
- lineNumber: parseInt(match[3]),
362
- columnNumber: parseInt(match[4])
473
+ functionName: initiatorFrame.functionName,
474
+ generatedUrl: initiatorFrame.url,
475
+ generatedLineNumber: initiatorFrame.lineNumber,
476
+ generatedColumnNumber: initiatorFrame.columnNumber,
477
+ stack: generatedStackPreview,
478
+ symbolicationStatus: canSymbolicateStack(generatedStackPreview) ? "pending" : "unavailable"
479
+ };
480
+ }
481
+ if (parsedFrames.length > 0) {
482
+ const fallbackStack = stackPreview.map(toGeneratedStackFrame);
483
+ return {
484
+ type: "other",
485
+ stack: fallbackStack,
486
+ symbolicationStatus: canSymbolicateStack(fallbackStack) ? "pending" : "unavailable"
363
487
  };
364
488
  }
365
489
  } catch {
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
- const getNitroModule = () => {
2
+ const nitroModule = (() => {
3
3
  try {
4
4
  return require("react-native-nitro-fetch");
5
5
  } catch {
6
6
  return null;
7
7
  }
8
+ })();
9
+ const getNitroModule = () => {
10
+ return nitroModule;
8
11
  };
9
12
  exports.getNitroModule = getNitroModule;
@@ -1,9 +1,12 @@
1
- const getNitroModule = () => {
1
+ const nitroModule = (() => {
2
2
  try {
3
3
  return require("react-native-nitro-fetch");
4
4
  } catch {
5
5
  return null;
6
6
  }
7
+ })();
8
+ const getNitroModule = () => {
9
+ return nitroModule;
7
10
  };
8
11
  export {
9
12
  getNitroModule as g
@@ -924,7 +924,9 @@ const useNetworkActivityAgentTools = ({
924
924
  agentBridge.useRozenitePluginAgentTool({
925
925
  pluginId: NETWORK_ACTIVITY_AGENT_PLUGIN_ID,
926
926
  tool: getResponseBodyTool,
927
- handler: async ({ requestId }) => {
927
+ handler: async ({
928
+ requestId
929
+ }) => {
928
930
  const record = state.getHttpRecord(requestId);
929
931
  if (!record) {
930
932
  throw new Error(`Unknown request "${requestId}"`);
@@ -951,6 +953,23 @@ const useNetworkActivityAgentTools = ({
951
953
  reason: "The plugin could not extract a text response body for this request."
952
954
  };
953
955
  }
956
+ if (typeof body !== "string") {
957
+ if (body.kind === "binary-too-large") {
958
+ return {
959
+ requestId,
960
+ available: false,
961
+ reason: `Response body exceeded the in-capture size cap (${body.size} bytes).`
962
+ };
963
+ }
964
+ return {
965
+ requestId,
966
+ available: true,
967
+ body: body.base64,
968
+ base64Encoded: true,
969
+ decoded: false,
970
+ mimeType: record.response?.contentType
971
+ };
972
+ }
954
973
  return {
955
974
  requestId,
956
975
  available: true,
@@ -922,7 +922,9 @@ const useNetworkActivityAgentTools = ({
922
922
  useRozenitePluginAgentTool({
923
923
  pluginId: NETWORK_ACTIVITY_AGENT_PLUGIN_ID,
924
924
  tool: getResponseBodyTool,
925
- handler: async ({ requestId }) => {
925
+ handler: async ({
926
+ requestId
927
+ }) => {
926
928
  const record = state.getHttpRecord(requestId);
927
929
  if (!record) {
928
930
  throw new Error(`Unknown request "${requestId}"`);
@@ -949,6 +951,23 @@ const useNetworkActivityAgentTools = ({
949
951
  reason: "The plugin could not extract a text response body for this request."
950
952
  };
951
953
  }
954
+ if (typeof body !== "string") {
955
+ if (body.kind === "binary-too-large") {
956
+ return {
957
+ requestId,
958
+ available: false,
959
+ reason: `Response body exceeded the in-capture size cap (${body.size} bytes).`
960
+ };
961
+ }
962
+ return {
963
+ requestId,
964
+ available: true,
965
+ body: body.base64,
966
+ base64Encoded: true,
967
+ decoded: false,
968
+ mimeType: record.response?.contentType
969
+ };
970
+ }
952
971
  return {
953
972
  requestId,
954
973
  available: true,
@@ -63,7 +63,7 @@ declare type HttpEventMap = {
63
63
  };
64
64
  'response-body': {
65
65
  requestId: RequestId;
66
- body: string | null;
66
+ body: ResponseBody;
67
67
  };
68
68
  'set-overrides': {
69
69
  overrides: [string, RequestOverride][];
@@ -76,9 +76,37 @@ declare type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
76
76
 
77
77
  declare type Initiator = {
78
78
  type: string;
79
+ symbolicationStatus?: 'pending' | 'complete' | 'failed' | 'unavailable';
80
+ symbolicationError?: string;
81
+ functionName?: string;
79
82
  url?: string;
80
83
  lineNumber?: number;
81
84
  columnNumber?: number;
85
+ generatedUrl?: string;
86
+ generatedLineNumber?: number;
87
+ generatedColumnNumber?: number;
88
+ codeFrame?: InitiatorCodeFrame | null;
89
+ stack?: InitiatorStackFrame[];
90
+ };
91
+
92
+ declare type InitiatorCodeFrame = {
93
+ content: string;
94
+ fileName: string;
95
+ location?: {
96
+ row: number;
97
+ column: number;
98
+ } | null;
99
+ };
100
+
101
+ declare type InitiatorStackFrame = {
102
+ functionName?: string;
103
+ url?: string;
104
+ lineNumber?: number;
105
+ columnNumber?: number;
106
+ generatedUrl?: string;
107
+ generatedLineNumber?: number;
108
+ generatedColumnNumber?: number;
109
+ isCollapsed?: boolean;
82
110
  };
83
111
 
84
112
  declare type InspectorsConfig = {
@@ -171,6 +199,14 @@ declare type Response_2 = {
171
199
  responseTime: Timestamp;
172
200
  };
173
201
 
202
+ declare type ResponseBody = string | {
203
+ kind: 'binary';
204
+ base64: string;
205
+ } | {
206
+ kind: 'binary-too-large';
207
+ size: number;
208
+ } | null;
209
+
174
210
  declare type SSECloseEvent = {
175
211
  type: 'sse-close';
176
212
  requestId: SSERequestId;
@@ -1 +1 @@
1
- {"name":"@rozenite/network-activity-plugin","version":"1.9.0","description":"Network Activity for Rozenite.","panels":[{"name":"Network Activity","source":"/devtools/App.html"}]}
1
+ {"name":"@rozenite/network-activity-plugin","version":"1.11.0","description":"Network Activity for Rozenite.","panels":[{"name":"Network Activity","source":"/devtools/App.html"}]}
@@ -402,7 +402,7 @@ declare type HttpEventMap = {
402
402
  };
403
403
  'response-body': {
404
404
  requestId: RequestId;
405
- body: string | null;
405
+ body: ResponseBody;
406
406
  };
407
407
  'set-overrides': {
408
408
  overrides: [string, RequestOverride][];
@@ -415,9 +415,37 @@ export declare type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | '
415
415
 
416
416
  export declare type Initiator = {
417
417
  type: string;
418
+ symbolicationStatus?: 'pending' | 'complete' | 'failed' | 'unavailable';
419
+ symbolicationError?: string;
420
+ functionName?: string;
418
421
  url?: string;
419
422
  lineNumber?: number;
420
423
  columnNumber?: number;
424
+ generatedUrl?: string;
425
+ generatedLineNumber?: number;
426
+ generatedColumnNumber?: number;
427
+ codeFrame?: InitiatorCodeFrame | null;
428
+ stack?: InitiatorStackFrame[];
429
+ };
430
+
431
+ declare type InitiatorCodeFrame = {
432
+ content: string;
433
+ fileName: string;
434
+ location?: {
435
+ row: number;
436
+ column: number;
437
+ } | null;
438
+ };
439
+
440
+ declare type InitiatorStackFrame = {
441
+ functionName?: string;
442
+ url?: string;
443
+ lineNumber?: number;
444
+ columnNumber?: number;
445
+ generatedUrl?: string;
446
+ generatedLineNumber?: number;
447
+ generatedColumnNumber?: number;
448
+ isCollapsed?: boolean;
421
449
  };
422
450
 
423
451
  export declare const NETWORK_ACTIVITY_AGENT_PLUGIN_ID = "@rozenite/network-activity-plugin";
@@ -1070,6 +1098,14 @@ declare type Response_2 = {
1070
1098
  };
1071
1099
  export { Response_2 as Response }
1072
1100
 
1101
+ declare type ResponseBody = string | {
1102
+ kind: 'binary';
1103
+ base64: string;
1104
+ } | {
1105
+ kind: 'binary-too-large';
1106
+ size: number;
1107
+ } | null;
1108
+
1073
1109
  declare type SSEAgentMessage = {
1074
1110
  id: string;
1075
1111
  type: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/network-activity-plugin",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Network Activity for Rozenite.",
5
5
  "type": "module",
6
6
  "main": "./dist/react-native/index.cjs",
@@ -16,9 +16,9 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "nanoevents": "^9.1.0",
19
- "@rozenite/agent-shared": "1.9.0",
20
- "@rozenite/agent-bridge": "1.9.0",
21
- "@rozenite/plugin-bridge": "1.9.0"
19
+ "@rozenite/agent-shared": "1.11.0",
20
+ "@rozenite/agent-bridge": "1.11.0",
21
+ "@rozenite/plugin-bridge": "1.11.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@floating-ui/react": "^0.26.0",
@@ -29,6 +29,9 @@
29
29
  "@radix-ui/react-tabs": "^1.1.12",
30
30
  "@tanstack/react-table": "^8.21.3",
31
31
  "@tanstack/react-virtual": "^3.0.0",
32
+ "@testing-library/dom": "^10.4.0",
33
+ "@testing-library/jest-dom": "^6.6.3",
34
+ "@testing-library/react": "^16.1.0",
32
35
  "@types/react": "~19.2.2",
33
36
  "@types/react-dom": "~19.1.7",
34
37
  "autoprefixer": "^10.4.21",
@@ -43,14 +46,15 @@
43
46
  "react-native": "0.83.1",
44
47
  "react-native-sse": "^1.2.1",
45
48
  "react-native-web": "^0.21.2",
49
+ "react-virtuoso": "^4.6.0",
46
50
  "tailwind-merge": "^3.3.1",
47
51
  "tailwindcss": "^3.4.17",
48
52
  "tailwindcss-animate": "^1.0.7",
49
53
  "typescript": "~5.9.3",
50
54
  "vite": "^7.3.1",
51
55
  "zustand": "^5.0.6",
52
- "@rozenite/vite-plugin": "1.9.0",
53
- "rozenite": "1.9.0"
56
+ "@rozenite/vite-plugin": "1.11.0",
57
+ "rozenite": "1.11.0"
54
58
  },
55
59
  "peerDependencies": {
56
60
  "react-native-nitro-fetch": "*",
@@ -86,6 +90,7 @@
86
90
  "build": "rozenite build",
87
91
  "dev": "rozenite dev",
88
92
  "typecheck": "tsc -p tsconfig.json --noEmit",
89
- "lint": "eslint ."
93
+ "lint": "eslint .",
94
+ "test": "vitest --run --passWithNoTests"
90
95
  }
91
96
  }
@@ -2,9 +2,7 @@ import { useEffect } from 'react';
2
2
  import { useRozenitePluginAgentTool } from '@rozenite/agent-bridge';
3
3
  import type { NetworkActivityDevToolsClient } from '../../shared/client';
4
4
  import type { NetworkInspector } from '../network-inspector';
5
- import {
6
- getNetworkActivityAgentState,
7
- } from './state';
5
+ import { getNetworkActivityAgentState } from './state';
8
6
  import {
9
7
  NETWORK_ACTIVITY_AGENT_PLUGIN_ID,
10
8
  type NetworkActivityGetResponseBodyResult,
@@ -195,7 +193,9 @@ export const useNetworkActivityAgentTools = ({
195
193
  useRozenitePluginAgentTool({
196
194
  pluginId: NETWORK_ACTIVITY_AGENT_PLUGIN_ID,
197
195
  tool: getResponseBodyTool,
198
- handler: async ({ requestId }): Promise<NetworkActivityGetResponseBodyResult> => {
196
+ handler: async ({
197
+ requestId,
198
+ }): Promise<NetworkActivityGetResponseBodyResult> => {
199
199
  const record = state.getHttpRecord(requestId);
200
200
  if (!record) {
201
201
  throw new Error(`Unknown request "${requestId}"`);
@@ -228,6 +228,24 @@ export const useNetworkActivityAgentTools = ({
228
228
  };
229
229
  }
230
230
 
231
+ if (typeof body !== 'string') {
232
+ if (body.kind === 'binary-too-large') {
233
+ return {
234
+ requestId,
235
+ available: false,
236
+ reason: `Response body exceeded the in-capture size cap (${body.size} bytes).`,
237
+ };
238
+ }
239
+ return {
240
+ requestId,
241
+ available: true,
242
+ body: body.base64,
243
+ base64Encoded: true,
244
+ decoded: false,
245
+ mimeType: record.response?.contentType,
246
+ };
247
+ }
248
+
231
249
  return {
232
250
  requestId,
233
251
  available: true,