@rozenite/network-activity-plugin 1.8.1 → 1.10.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 (72) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/devtools/App.html +2 -2
  3. package/dist/devtools/assets/{App-m6xge0az.css → App-CUXU0mup.css} +152 -2
  4. package/dist/devtools/assets/{App-B3xlUjs6.js → App-DsimzJvx.js} +6833 -966
  5. package/dist/react-native/chunks/boot-recording.cjs +156 -28
  6. package/dist/react-native/chunks/boot-recording.js +156 -28
  7. package/dist/react-native/chunks/get-nitro-module.cjs +12 -0
  8. package/dist/react-native/chunks/get-nitro-module.js +13 -0
  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 +39 -3
  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 +209 -25
  18. package/src/react-native/network-inspector.ts +2 -2
  19. package/src/react-native/nitro-fetch/get-nitro-module.ts +13 -0
  20. package/src/react-native/nitro-fetch/nitro-network-inspector.ts +10 -11
  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 +366 -58
  24. package/src/ui/components/HexView.tsx +54 -0
  25. package/src/ui/components/MetadataCard.tsx +95 -0
  26. package/src/ui/components/RequestList.tsx +192 -34
  27. package/src/ui/components/SidePanel.tsx +42 -1
  28. package/src/ui/components/ViewToggle.tsx +44 -0
  29. package/src/ui/components/XmlTree.tsx +160 -0
  30. package/src/ui/components/__tests__/CodeBlock.test.tsx +89 -0
  31. package/src/ui/components/__tests__/HexView.test.tsx +41 -0
  32. package/src/ui/components/__tests__/MetadataCard.test.tsx +107 -0
  33. package/src/ui/components/__tests__/ViewToggle.test.tsx +80 -0
  34. package/src/ui/components/__tests__/XmlTree.test.tsx +149 -0
  35. package/src/ui/response-renderers/__tests__/binary-too-large.test.tsx +56 -0
  36. package/src/ui/response-renderers/__tests__/binary.test.tsx +96 -0
  37. package/src/ui/response-renderers/__tests__/dispatch.test.ts +124 -0
  38. package/src/ui/response-renderers/__tests__/html.test.tsx +101 -0
  39. package/src/ui/response-renderers/__tests__/image.test.tsx +73 -0
  40. package/src/ui/response-renderers/__tests__/json.test.tsx +95 -0
  41. package/src/ui/response-renderers/__tests__/svg.test.tsx +46 -0
  42. package/src/ui/response-renderers/__tests__/xml.test.tsx +100 -0
  43. package/src/ui/response-renderers/binary-too-large.tsx +36 -0
  44. package/src/ui/response-renderers/binary.tsx +31 -0
  45. package/src/ui/response-renderers/empty.tsx +14 -0
  46. package/src/ui/response-renderers/html.tsx +36 -0
  47. package/src/ui/response-renderers/image.tsx +37 -0
  48. package/src/ui/response-renderers/index.ts +55 -0
  49. package/src/ui/response-renderers/json.tsx +40 -0
  50. package/src/ui/response-renderers/svg.tsx +27 -0
  51. package/src/ui/response-renderers/text-fallback.tsx +14 -0
  52. package/src/ui/response-renderers/types.ts +38 -0
  53. package/src/ui/response-renderers/unknown.tsx +18 -0
  54. package/src/ui/response-renderers/xml.tsx +46 -0
  55. package/src/ui/state/derived.ts +12 -0
  56. package/src/ui/state/model.ts +6 -1
  57. package/src/ui/state/store.ts +39 -2
  58. package/src/ui/tabs/InitiatorTab.tsx +230 -0
  59. package/src/ui/tabs/ResponseTab.tsx +80 -96
  60. package/src/ui/tabs/__tests__/ResponseTab.test.tsx +102 -0
  61. package/src/ui/utils/__tests__/download.test.ts +115 -0
  62. package/src/ui/utils/__tests__/hex.test.ts +84 -0
  63. package/src/ui/utils/__tests__/symbolication.test.ts +207 -0
  64. package/src/ui/utils/download.ts +154 -0
  65. package/src/ui/utils/hex.ts +59 -0
  66. package/src/ui/utils/initiator.ts +136 -0
  67. package/src/ui/utils/symbolication.ts +248 -0
  68. package/src/ui/views/InspectorView.tsx +8 -5
  69. package/src/utils/__tests__/getContentTypeMimeType.test.ts +65 -0
  70. package/src/utils/getContentTypeMimeType.ts +28 -0
  71. package/vite.config.ts +9 -1
  72. package/vitest.setup.ts +31 -0
@@ -3,6 +3,7 @@ const nanoevents = require("nanoevents");
3
3
  const eventSource = require("./event-source.cjs");
4
4
  const reactNative = require("react-native");
5
5
  const WebSocketInterceptor = require("react-native/Libraries/WebSocket/WebSocketInterceptor");
6
+ const getNitroModule = require("./get-nitro-module.cjs");
6
7
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
7
8
  const WebSocketInterceptor__default = /* @__PURE__ */ _interopDefault(WebSocketInterceptor);
8
9
  const REQUEST_TTL = 1e3 * 60 * 5;
@@ -184,10 +185,6 @@ function safeStringify(data) {
184
185
  const getStringSizeInBytes = (value) => {
185
186
  return new TextEncoder().encode(value).length;
186
187
  };
187
- const isBlob = (value) => value instanceof Blob;
188
- const isArrayBuffer = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
189
- const isFormData = (value) => value instanceof FormData;
190
- const isNullOrUndefined = (value) => value === null || value === void 0;
191
188
  function getHttpHeader(headers, name) {
192
189
  const lowerName = name.toLowerCase();
193
190
  for (const key in headers) {
@@ -197,6 +194,9 @@ function getHttpHeader(headers, name) {
197
194
  }
198
195
  return void 0;
199
196
  }
197
+ function normalizeContentType(contentType) {
198
+ return contentType.split(";")[0].trim().toLowerCase();
199
+ }
200
200
  function getContentTypeMime(headers) {
201
201
  const contentType = getHttpHeader(headers, "content-type");
202
202
  if (!contentType) {
@@ -206,6 +206,24 @@ function getContentTypeMime(headers) {
206
206
  const actualValue = Array.isArray(value) ? value[0] : value;
207
207
  return actualValue.split(";")[0].trim();
208
208
  }
209
+ function isJsonContentType(contentType) {
210
+ if (!contentType) {
211
+ return false;
212
+ }
213
+ const mimeType = normalizeContentType(contentType);
214
+ return mimeType === "application/json" || mimeType.endsWith("+json");
215
+ }
216
+ function isXmlContentType(contentType) {
217
+ if (!contentType) {
218
+ return false;
219
+ }
220
+ const mimeType = normalizeContentType(contentType);
221
+ return mimeType === "application/xml" || mimeType === "text/xml" || mimeType.endsWith("+xml");
222
+ }
223
+ const isBlob = (value) => value instanceof Blob;
224
+ const isArrayBuffer = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
225
+ const isFormData = (value) => value instanceof FormData;
226
+ const isNullOrUndefined = (value) => value === null || value === void 0;
209
227
  const getContentType$1 = (request) => {
210
228
  const responseHeaders = request.responseHeaders;
211
229
  const responseType = request.responseType;
@@ -316,6 +334,30 @@ const getResponseSize = (request) => {
316
334
  return null;
317
335
  }
318
336
  };
337
+ const BINARY_CAPTURE_SIZE_CAP = 5 * 1024 * 1024;
338
+ const readBlobAsText = (blob) => new Promise((resolve) => {
339
+ const reader = new FileReader();
340
+ reader.onload = () => resolve(reader.result);
341
+ reader.readAsText(blob);
342
+ });
343
+ const readBlobAsBase64 = (blob) => new Promise((resolve) => {
344
+ const reader = new FileReader();
345
+ reader.onload = () => {
346
+ const dataUrl = reader.result;
347
+ resolve(dataUrl.substring(dataUrl.indexOf(",") + 1));
348
+ };
349
+ reader.readAsDataURL(blob);
350
+ });
351
+ const ARRAY_BUFFER_BASE64_CHUNK = 32768;
352
+ const arrayBufferToBase64 = (buffer) => {
353
+ const bytes = new Uint8Array(buffer);
354
+ let binary = "";
355
+ for (let i = 0; i < bytes.length; i += ARRAY_BUFFER_BASE64_CHUNK) {
356
+ const chunk = bytes.subarray(i, i + ARRAY_BUFFER_BASE64_CHUNK);
357
+ binary += String.fromCharCode.apply(null, chunk);
358
+ }
359
+ return btoa(binary);
360
+ };
319
361
  const getResponseBody = async (request) => {
320
362
  const responseType = request.responseType;
321
363
  if (responseType === "" || responseType === "text") {
@@ -323,35 +365,128 @@ const getResponseBody = async (request) => {
323
365
  }
324
366
  if (responseType === "blob") {
325
367
  const contentType = request.getResponseHeader("Content-Type") || "";
326
- if (contentType.startsWith("text/") || contentType.startsWith("application/json")) {
327
- return new Promise((resolve) => {
328
- const reader = new FileReader();
329
- reader.onload = () => {
330
- resolve(reader.result);
331
- };
332
- reader.readAsText(request.response);
333
- });
368
+ if (contentType.startsWith("text/") || isJsonContentType(contentType) || isXmlContentType(contentType)) {
369
+ return readBlobAsText(request.response);
334
370
  }
371
+ const blob = request.response;
372
+ if (blob.size > BINARY_CAPTURE_SIZE_CAP) {
373
+ return { kind: "binary-too-large", size: blob.size };
374
+ }
375
+ return { kind: "binary", base64: await readBlobAsBase64(blob) };
376
+ }
377
+ if (responseType === "arraybuffer") {
378
+ const buffer = request.response;
379
+ if (!buffer || buffer.byteLength === 0) {
380
+ return null;
381
+ }
382
+ if (buffer.byteLength > BINARY_CAPTURE_SIZE_CAP) {
383
+ return { kind: "binary-too-large", size: buffer.byteLength };
384
+ }
385
+ return { kind: "binary", base64: arrayBufferToBase64(buffer) };
335
386
  }
336
387
  if (responseType === "json") {
337
388
  return safeStringify(request.response);
338
389
  }
339
390
  return null;
340
391
  };
392
+ const STACK_PREVIEW_FRAME_LIMIT = 8;
393
+ const INITIATOR_STACK_FRAME_OFFSET = 3;
394
+ const parseStackLocation = (location) => {
395
+ const match = location.match(/^(.*):(\d+):(\d+)$/);
396
+ if (!match) {
397
+ return null;
398
+ }
399
+ return {
400
+ url: match[1],
401
+ lineNumber: Number.parseInt(match[2], 10),
402
+ columnNumber: Number.parseInt(match[3], 10)
403
+ };
404
+ };
405
+ const normalizeFunctionName = (functionName) => {
406
+ const trimmedFunctionName = functionName?.trim();
407
+ return trimmedFunctionName && trimmedFunctionName !== "<anonymous>" && trimmedFunctionName !== "anonymous" && trimmedFunctionName !== "<unknown>" ? trimmedFunctionName : void 0;
408
+ };
409
+ const parseStackFrame = (line) => {
410
+ const trimmedLine = line.trim();
411
+ if (!trimmedLine) {
412
+ return null;
413
+ }
414
+ let functionName;
415
+ let location;
416
+ const v8FunctionFrame = trimmedLine.match(/^at\s+(.*?)\s+\((.*)\)$/);
417
+ if (v8FunctionFrame) {
418
+ functionName = v8FunctionFrame[1];
419
+ location = v8FunctionFrame[2];
420
+ } else {
421
+ const v8LocationFrame = trimmedLine.match(/^at\s+(.*)$/);
422
+ const jscFrame = trimmedLine.match(/^(.*?)@(.*)$/);
423
+ if (v8LocationFrame) {
424
+ location = v8LocationFrame[1];
425
+ } else if (jscFrame) {
426
+ functionName = jscFrame[1];
427
+ location = jscFrame[2];
428
+ }
429
+ }
430
+ if (!location) {
431
+ return null;
432
+ }
433
+ const parsedLocation = parseStackLocation(location);
434
+ if (!parsedLocation) {
435
+ return null;
436
+ }
437
+ return {
438
+ functionName: normalizeFunctionName(functionName),
439
+ ...parsedLocation
440
+ };
441
+ };
442
+ const toGeneratedStackFrame = (frame) => ({
443
+ functionName: frame.functionName,
444
+ generatedUrl: frame.url,
445
+ generatedLineNumber: frame.lineNumber,
446
+ generatedColumnNumber: frame.columnNumber
447
+ });
448
+ const getGeneratedFrameLocation = (frame) => ({
449
+ url: frame.generatedUrl ?? frame.url,
450
+ lineNumber: frame.generatedLineNumber ?? frame.lineNumber,
451
+ columnNumber: frame.generatedColumnNumber ?? frame.columnNumber
452
+ });
453
+ const canSymbolicateStack = (stack) => stack?.some(
454
+ (frame) => getGeneratedFrameLocation(frame).url?.startsWith("http")
455
+ ) ?? false;
456
+ const getStackPreview = (frames) => {
457
+ const callerFrames = frames.slice(INITIATOR_STACK_FRAME_OFFSET);
458
+ return (callerFrames.length > 0 ? callerFrames : frames).slice(
459
+ 0,
460
+ STACK_PREVIEW_FRAME_LIMIT
461
+ );
462
+ };
341
463
  const getInitiatorFromStack = () => {
342
464
  try {
343
465
  const stack = new Error().stack;
344
466
  if (!stack) {
345
467
  return { type: "other" };
346
468
  }
347
- const line = stack.split("\n")[9];
348
- const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
349
- if (match) {
469
+ const parsedFrames = stack.split("\n").map(parseStackFrame).filter((frame) => frame !== null);
470
+ const stackPreview = getStackPreview(parsedFrames);
471
+ const initiatorFrame = stackPreview[0];
472
+ const generatedStackPreview = stackPreview.map(toGeneratedStackFrame);
473
+ if (initiatorFrame?.url) {
350
474
  return {
351
475
  type: "script",
352
- url: match[2],
353
- lineNumber: parseInt(match[3]),
354
- columnNumber: parseInt(match[4])
476
+ functionName: initiatorFrame.functionName,
477
+ generatedUrl: initiatorFrame.url,
478
+ generatedLineNumber: initiatorFrame.lineNumber,
479
+ generatedColumnNumber: initiatorFrame.columnNumber,
480
+ stack: generatedStackPreview,
481
+ symbolicationStatus: canSymbolicateStack(generatedStackPreview) ? "pending" : "unavailable"
482
+ };
483
+ }
484
+ if (parsedFrames.length > 0) {
485
+ const fallbackStack = stackPreview.map(toGeneratedStackFrame);
486
+ return {
487
+ type: "other",
488
+ stack: fallbackStack,
489
+ symbolicationStatus: canSymbolicateStack(fallbackStack) ? "pending" : "unavailable"
355
490
  };
356
491
  }
357
492
  } catch {
@@ -367,7 +502,7 @@ const setupRequestOverride = (overridesRegistry, request) => {
367
502
  Object.defineProperty(request, "response", { writable: true });
368
503
  Object.defineProperty(request, "responseText", { writable: true });
369
504
  const contentType = getContentType$1(request);
370
- if (contentType === "application/json") {
505
+ if (isJsonContentType(contentType)) {
371
506
  request.responseType = "json";
372
507
  } else if (contentType === "text/plain") {
373
508
  request.responseType = "text";
@@ -1026,13 +1161,6 @@ const NITRO_NETWORK_EVENTS = [
1026
1161
  "websocket-message-received",
1027
1162
  "websocket-error"
1028
1163
  ];
1029
- const loadNitroModule = () => {
1030
- try {
1031
- return require("react-native-nitro-fetch");
1032
- } catch {
1033
- return null;
1034
- }
1035
- };
1036
1164
  const timestampOrigin = typeof performance !== "undefined" && typeof performance.timeOrigin === "number" ? performance.timeOrigin : Date.now() - performance.now();
1037
1165
  const toEpochTime = (timestamp) => Math.round(timestampOrigin + timestamp);
1038
1166
  const toHeaders = (headers) => {
@@ -1062,7 +1190,7 @@ const getContentType = (headers) => {
1062
1190
  return headers.find((header) => header.key.toLowerCase() === "content-type")?.value ?? "text/plain";
1063
1191
  };
1064
1192
  const normalizeReadyState = (readyState) => readyState.toUpperCase();
1065
- const createNitroNetworkInspector = (getNitroModule = loadNitroModule) => {
1193
+ const createNitroNetworkInspector = (getNitroModule$1 = getNitroModule.getNitroModule) => {
1066
1194
  const eventEmitter = nanoevents.createNanoEvents();
1067
1195
  const previousEntries = /* @__PURE__ */ new Map();
1068
1196
  const responseBodies = /* @__PURE__ */ new Map();
@@ -1209,7 +1337,7 @@ const createNitroNetworkInspector = (getNitroModule = loadNitroModule) => {
1209
1337
  if (unsubscribe) {
1210
1338
  return;
1211
1339
  }
1212
- nitroModule = getNitroModule();
1340
+ nitroModule = getNitroModule$1();
1213
1341
  if (!nitroModule) {
1214
1342
  return;
1215
1343
  }
@@ -2,6 +2,7 @@ import { createNanoEvents } from "nanoevents";
2
2
  import { g as getEventSource } from "./event-source.js";
3
3
  import { Platform } from "react-native";
4
4
  import WebSocketInterceptor from "react-native/Libraries/WebSocket/WebSocketInterceptor";
5
+ import { g as getNitroModule } from "./get-nitro-module.js";
5
6
  const REQUEST_TTL = 1e3 * 60 * 5;
6
7
  const getNetworkRequestsRegistry = () => {
7
8
  const registry = /* @__PURE__ */ new Map();
@@ -181,10 +182,6 @@ function safeStringify(data) {
181
182
  const getStringSizeInBytes = (value) => {
182
183
  return new TextEncoder().encode(value).length;
183
184
  };
184
- const isBlob = (value) => value instanceof Blob;
185
- const isArrayBuffer = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
186
- const isFormData = (value) => value instanceof FormData;
187
- const isNullOrUndefined = (value) => value === null || value === void 0;
188
185
  function getHttpHeader(headers, name) {
189
186
  const lowerName = name.toLowerCase();
190
187
  for (const key in headers) {
@@ -194,6 +191,9 @@ function getHttpHeader(headers, name) {
194
191
  }
195
192
  return void 0;
196
193
  }
194
+ function normalizeContentType(contentType) {
195
+ return contentType.split(";")[0].trim().toLowerCase();
196
+ }
197
197
  function getContentTypeMime(headers) {
198
198
  const contentType = getHttpHeader(headers, "content-type");
199
199
  if (!contentType) {
@@ -203,6 +203,24 @@ function getContentTypeMime(headers) {
203
203
  const actualValue = Array.isArray(value) ? value[0] : value;
204
204
  return actualValue.split(";")[0].trim();
205
205
  }
206
+ function isJsonContentType(contentType) {
207
+ if (!contentType) {
208
+ return false;
209
+ }
210
+ const mimeType = normalizeContentType(contentType);
211
+ return mimeType === "application/json" || mimeType.endsWith("+json");
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
+ }
220
+ const isBlob = (value) => value instanceof Blob;
221
+ const isArrayBuffer = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
222
+ const isFormData = (value) => value instanceof FormData;
223
+ const isNullOrUndefined = (value) => value === null || value === void 0;
206
224
  const getContentType$1 = (request) => {
207
225
  const responseHeaders = request.responseHeaders;
208
226
  const responseType = request.responseType;
@@ -313,6 +331,30 @@ const getResponseSize = (request) => {
313
331
  return null;
314
332
  }
315
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
+ };
316
358
  const getResponseBody = async (request) => {
317
359
  const responseType = request.responseType;
318
360
  if (responseType === "" || responseType === "text") {
@@ -320,35 +362,128 @@ const getResponseBody = async (request) => {
320
362
  }
321
363
  if (responseType === "blob") {
322
364
  const contentType = request.getResponseHeader("Content-Type") || "";
323
- if (contentType.startsWith("text/") || contentType.startsWith("application/json")) {
324
- return new Promise((resolve) => {
325
- const reader = new FileReader();
326
- reader.onload = () => {
327
- resolve(reader.result);
328
- };
329
- reader.readAsText(request.response);
330
- });
365
+ if (contentType.startsWith("text/") || isJsonContentType(contentType) || isXmlContentType(contentType)) {
366
+ return readBlobAsText(request.response);
331
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;
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) };
332
383
  }
333
384
  if (responseType === "json") {
334
385
  return safeStringify(request.response);
335
386
  }
336
387
  return null;
337
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
+ };
338
460
  const getInitiatorFromStack = () => {
339
461
  try {
340
462
  const stack = new Error().stack;
341
463
  if (!stack) {
342
464
  return { type: "other" };
343
465
  }
344
- const line = stack.split("\n")[9];
345
- const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
346
- 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) {
347
471
  return {
348
472
  type: "script",
349
- url: match[2],
350
- lineNumber: parseInt(match[3]),
351
- 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"
352
487
  };
353
488
  }
354
489
  } catch {
@@ -364,7 +499,7 @@ const setupRequestOverride = (overridesRegistry, request) => {
364
499
  Object.defineProperty(request, "response", { writable: true });
365
500
  Object.defineProperty(request, "responseText", { writable: true });
366
501
  const contentType = getContentType$1(request);
367
- if (contentType === "application/json") {
502
+ if (isJsonContentType(contentType)) {
368
503
  request.responseType = "json";
369
504
  } else if (contentType === "text/plain") {
370
505
  request.responseType = "text";
@@ -1023,13 +1158,6 @@ const NITRO_NETWORK_EVENTS = [
1023
1158
  "websocket-message-received",
1024
1159
  "websocket-error"
1025
1160
  ];
1026
- const loadNitroModule = () => {
1027
- try {
1028
- return require("react-native-nitro-fetch");
1029
- } catch {
1030
- return null;
1031
- }
1032
- };
1033
1161
  const timestampOrigin = typeof performance !== "undefined" && typeof performance.timeOrigin === "number" ? performance.timeOrigin : Date.now() - performance.now();
1034
1162
  const toEpochTime = (timestamp) => Math.round(timestampOrigin + timestamp);
1035
1163
  const toHeaders = (headers) => {
@@ -1059,7 +1187,7 @@ const getContentType = (headers) => {
1059
1187
  return headers.find((header) => header.key.toLowerCase() === "content-type")?.value ?? "text/plain";
1060
1188
  };
1061
1189
  const normalizeReadyState = (readyState) => readyState.toUpperCase();
1062
- const createNitroNetworkInspector = (getNitroModule = loadNitroModule) => {
1190
+ const createNitroNetworkInspector = (getNitroModule$1 = getNitroModule) => {
1063
1191
  const eventEmitter = createNanoEvents();
1064
1192
  const previousEntries = /* @__PURE__ */ new Map();
1065
1193
  const responseBodies = /* @__PURE__ */ new Map();
@@ -1206,7 +1334,7 @@ const createNitroNetworkInspector = (getNitroModule = loadNitroModule) => {
1206
1334
  if (unsubscribe) {
1207
1335
  return;
1208
1336
  }
1209
- nitroModule = getNitroModule();
1337
+ nitroModule = getNitroModule$1();
1210
1338
  if (!nitroModule) {
1211
1339
  return;
1212
1340
  }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ const nitroModule = (() => {
3
+ try {
4
+ return require("react-native-nitro-fetch");
5
+ } catch {
6
+ return null;
7
+ }
8
+ })();
9
+ const getNitroModule = () => {
10
+ return nitroModule;
11
+ };
12
+ exports.getNitroModule = getNitroModule;
@@ -0,0 +1,13 @@
1
+ const nitroModule = (() => {
2
+ try {
3
+ return require("react-native-nitro-fetch");
4
+ } catch {
5
+ return null;
6
+ }
7
+ })();
8
+ const getNitroModule = () => {
9
+ return nitroModule;
10
+ };
11
+ export {
12
+ getNitroModule as g
13
+ };
@@ -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,