@rozenite/network-activity-plugin 1.8.0 → 1.9.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @rozenite/network-activity-plugin
2
2
 
3
+ ## 1.9.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#240](https://github.com/callstackincubator/rozenite/pull/240) [`0e2a4db`](https://github.com/callstackincubator/rozenite/commit/0e2a4db7943f004b7f52422fbe23b679829e5b57) Thanks [@V3RON](https://github.com/V3RON)! - Rozenite now treats `application/*+json` responses as JSON in Network Activity, so vendor-specific JSON payloads render correctly instead of falling back to plain text.
8
+
9
+ - [#260](https://github.com/callstackincubator/rozenite/pull/260) [`9cea370`](https://github.com/callstackincubator/rozenite/commit/9cea370c441595eba266f800901656370bb608f8) Thanks [@V3RON](https://github.com/V3RON)! - Fix `react-native-nitro-fetch` not being resolved correctly in Metro by isolating the optional dependency import into its own bundle chunk. This ensures the network inspector works reliably even when `react-native-nitro-fetch` is not installed.
10
+
11
+ - Updated dependencies []:
12
+ - @rozenite/agent-bridge@1.9.0
13
+ - @rozenite/agent-shared@1.9.0
14
+ - @rozenite/plugin-bridge@1.9.0
15
+
16
+ ## 1.8.1
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies []:
21
+ - @rozenite/agent-bridge@1.8.1
22
+ - @rozenite/agent-shared@1.8.1
23
+ - @rozenite/plugin-bridge@1.8.1
24
+
3
25
  ## 1.8.0
4
26
 
5
27
  ### Minor Changes
@@ -22,7 +22,7 @@
22
22
  <script>
23
23
  var __ROZENITE_PANEL__ = true;
24
24
  </script>
25
- <script type="module" crossorigin src="../devtools/assets/App-B3xlUjs6.js"></script>
25
+ <script type="module" crossorigin src="../devtools/assets/App-hSoryVpJ.js"></script>
26
26
  <link rel="stylesheet" crossorigin href="../devtools/assets/App-m6xge0az.css">
27
27
  </head>
28
28
  <body>
@@ -20772,6 +20772,9 @@ function getHttpHeader(headers, name) {
20772
20772
  }
20773
20773
  return void 0;
20774
20774
  }
20775
+ function normalizeContentType(contentType) {
20776
+ return contentType.split(";")[0].trim().toLowerCase();
20777
+ }
20775
20778
  function getContentTypeMime(headers) {
20776
20779
  const contentType = getHttpHeader(headers, "content-type");
20777
20780
  if (!contentType) {
@@ -20781,6 +20784,13 @@ function getContentTypeMime(headers) {
20781
20784
  const actualValue = Array.isArray(value) ? value[0] : value;
20782
20785
  return actualValue.split(";")[0].trim();
20783
20786
  }
20787
+ function isJsonContentType(contentType) {
20788
+ if (!contentType) {
20789
+ return false;
20790
+ }
20791
+ const mimeType = normalizeContentType(contentType);
20792
+ return mimeType === "application/json" || mimeType.endsWith("+json");
20793
+ }
20784
20794
  function inferContentTypeFromPostData(postData) {
20785
20795
  if (postData?.type === "form-data") {
20786
20796
  return "multipart/form-data";
@@ -35330,7 +35340,7 @@ const ResponseTab = ({
35330
35340
  }
35331
35341
  );
35332
35342
  }
35333
- if (type.startsWith("application/json")) {
35343
+ if (isJsonContentType(type)) {
35334
35344
  let bodyContent;
35335
35345
  try {
35336
35346
  const jsonData = JSON.parse(data);
@@ -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,17 @@ 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
+ const isBlob = (value) => value instanceof Blob;
217
+ const isArrayBuffer = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
218
+ const isFormData = (value) => value instanceof FormData;
219
+ const isNullOrUndefined = (value) => value === null || value === void 0;
209
220
  const getContentType$1 = (request) => {
210
221
  const responseHeaders = request.responseHeaders;
211
222
  const responseType = request.responseType;
@@ -323,7 +334,7 @@ const getResponseBody = async (request) => {
323
334
  }
324
335
  if (responseType === "blob") {
325
336
  const contentType = request.getResponseHeader("Content-Type") || "";
326
- if (contentType.startsWith("text/") || contentType.startsWith("application/json")) {
337
+ if (contentType.startsWith("text/") || isJsonContentType(contentType)) {
327
338
  return new Promise((resolve) => {
328
339
  const reader = new FileReader();
329
340
  reader.onload = () => {
@@ -367,7 +378,7 @@ const setupRequestOverride = (overridesRegistry, request) => {
367
378
  Object.defineProperty(request, "response", { writable: true });
368
379
  Object.defineProperty(request, "responseText", { writable: true });
369
380
  const contentType = getContentType$1(request);
370
- if (contentType === "application/json") {
381
+ if (isJsonContentType(contentType)) {
371
382
  request.responseType = "json";
372
383
  } else if (contentType === "text/plain") {
373
384
  request.responseType = "text";
@@ -1026,13 +1037,6 @@ const NITRO_NETWORK_EVENTS = [
1026
1037
  "websocket-message-received",
1027
1038
  "websocket-error"
1028
1039
  ];
1029
- const loadNitroModule = () => {
1030
- try {
1031
- return require("react-native-nitro-fetch");
1032
- } catch {
1033
- return null;
1034
- }
1035
- };
1036
1040
  const timestampOrigin = typeof performance !== "undefined" && typeof performance.timeOrigin === "number" ? performance.timeOrigin : Date.now() - performance.now();
1037
1041
  const toEpochTime = (timestamp) => Math.round(timestampOrigin + timestamp);
1038
1042
  const toHeaders = (headers) => {
@@ -1062,7 +1066,7 @@ const getContentType = (headers) => {
1062
1066
  return headers.find((header) => header.key.toLowerCase() === "content-type")?.value ?? "text/plain";
1063
1067
  };
1064
1068
  const normalizeReadyState = (readyState) => readyState.toUpperCase();
1065
- const createNitroNetworkInspector = (getNitroModule = loadNitroModule) => {
1069
+ const createNitroNetworkInspector = (getNitroModule$1 = getNitroModule.getNitroModule) => {
1066
1070
  const eventEmitter = nanoevents.createNanoEvents();
1067
1071
  const previousEntries = /* @__PURE__ */ new Map();
1068
1072
  const responseBodies = /* @__PURE__ */ new Map();
@@ -1209,7 +1213,7 @@ const createNitroNetworkInspector = (getNitroModule = loadNitroModule) => {
1209
1213
  if (unsubscribe) {
1210
1214
  return;
1211
1215
  }
1212
- nitroModule = getNitroModule();
1216
+ nitroModule = getNitroModule$1();
1213
1217
  if (!nitroModule) {
1214
1218
  return;
1215
1219
  }
@@ -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,17 @@ 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
+ const isBlob = (value) => value instanceof Blob;
214
+ const isArrayBuffer = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
215
+ const isFormData = (value) => value instanceof FormData;
216
+ const isNullOrUndefined = (value) => value === null || value === void 0;
206
217
  const getContentType$1 = (request) => {
207
218
  const responseHeaders = request.responseHeaders;
208
219
  const responseType = request.responseType;
@@ -320,7 +331,7 @@ const getResponseBody = async (request) => {
320
331
  }
321
332
  if (responseType === "blob") {
322
333
  const contentType = request.getResponseHeader("Content-Type") || "";
323
- if (contentType.startsWith("text/") || contentType.startsWith("application/json")) {
334
+ if (contentType.startsWith("text/") || isJsonContentType(contentType)) {
324
335
  return new Promise((resolve) => {
325
336
  const reader = new FileReader();
326
337
  reader.onload = () => {
@@ -364,7 +375,7 @@ const setupRequestOverride = (overridesRegistry, request) => {
364
375
  Object.defineProperty(request, "response", { writable: true });
365
376
  Object.defineProperty(request, "responseText", { writable: true });
366
377
  const contentType = getContentType$1(request);
367
- if (contentType === "application/json") {
378
+ if (isJsonContentType(contentType)) {
368
379
  request.responseType = "json";
369
380
  } else if (contentType === "text/plain") {
370
381
  request.responseType = "text";
@@ -1023,13 +1034,6 @@ const NITRO_NETWORK_EVENTS = [
1023
1034
  "websocket-message-received",
1024
1035
  "websocket-error"
1025
1036
  ];
1026
- const loadNitroModule = () => {
1027
- try {
1028
- return require("react-native-nitro-fetch");
1029
- } catch {
1030
- return null;
1031
- }
1032
- };
1033
1037
  const timestampOrigin = typeof performance !== "undefined" && typeof performance.timeOrigin === "number" ? performance.timeOrigin : Date.now() - performance.now();
1034
1038
  const toEpochTime = (timestamp) => Math.round(timestampOrigin + timestamp);
1035
1039
  const toHeaders = (headers) => {
@@ -1059,7 +1063,7 @@ const getContentType = (headers) => {
1059
1063
  return headers.find((header) => header.key.toLowerCase() === "content-type")?.value ?? "text/plain";
1060
1064
  };
1061
1065
  const normalizeReadyState = (readyState) => readyState.toUpperCase();
1062
- const createNitroNetworkInspector = (getNitroModule = loadNitroModule) => {
1066
+ const createNitroNetworkInspector = (getNitroModule$1 = getNitroModule) => {
1063
1067
  const eventEmitter = createNanoEvents();
1064
1068
  const previousEntries = /* @__PURE__ */ new Map();
1065
1069
  const responseBodies = /* @__PURE__ */ new Map();
@@ -1206,7 +1210,7 @@ const createNitroNetworkInspector = (getNitroModule = loadNitroModule) => {
1206
1210
  if (unsubscribe) {
1207
1211
  return;
1208
1212
  }
1209
- nitroModule = getNitroModule();
1213
+ nitroModule = getNitroModule$1();
1210
1214
  if (!nitroModule) {
1211
1215
  return;
1212
1216
  }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ const getNitroModule = () => {
3
+ try {
4
+ return require("react-native-nitro-fetch");
5
+ } catch {
6
+ return null;
7
+ }
8
+ };
9
+ exports.getNitroModule = getNitroModule;
@@ -0,0 +1,10 @@
1
+ const getNitroModule = () => {
2
+ try {
3
+ return require("react-native-nitro-fetch");
4
+ } catch {
5
+ return null;
6
+ }
7
+ };
8
+ export {
9
+ getNitroModule as g
10
+ };
@@ -216,7 +216,7 @@ declare type SSERequestId = string;
216
216
 
217
217
  declare type Timestamp = number;
218
218
 
219
- export declare let useNetworkActivityDevTools: useNetworkActivityDevTools_2;
219
+ export declare let useNetworkActivityDevTools: typeof useNetworkActivityDevTools_2;
220
220
 
221
221
  declare const useNetworkActivityDevTools_2: (config?: NetworkActivityDevToolsConfig) => RozeniteDevToolsClient<NetworkActivityEventMap> | null;
222
222
 
@@ -298,7 +298,7 @@ declare type WebSocketOpenEvent = {
298
298
  source?: NetworkEventSource;
299
299
  };
300
300
 
301
- export declare let withOnBootNetworkActivityRecording: withOnBootNetworkActivityRecording_2;
301
+ export declare let withOnBootNetworkActivityRecording: typeof withOnBootNetworkActivityRecording_2;
302
302
 
303
303
  /**
304
304
  * Enable network activity recording during app boot, before DevTools connects.
@@ -1 +1 @@
1
- {"name":"@rozenite/network-activity-plugin","version":"1.8.0","description":"Network Activity for Rozenite.","panels":[{"name":"Network Activity","source":"/devtools/App.html"}]}
1
+ {"name":"@rozenite/network-activity-plugin","version":"1.9.0","description":"Network Activity for Rozenite.","panels":[{"name":"Network Activity","source":"/devtools/App.html"}]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/network-activity-plugin",
3
- "version": "1.8.0",
3
+ "version": "1.9.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.8.0",
20
- "@rozenite/agent-bridge": "1.8.0",
21
- "@rozenite/plugin-bridge": "1.8.0"
19
+ "@rozenite/agent-shared": "1.9.0",
20
+ "@rozenite/agent-bridge": "1.9.0",
21
+ "@rozenite/plugin-bridge": "1.9.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@floating-ui/react": "^0.26.0",
@@ -49,8 +49,8 @@
49
49
  "typescript": "~5.9.3",
50
50
  "vite": "^7.3.1",
51
51
  "zustand": "^5.0.6",
52
- "@rozenite/vite-plugin": "1.8.0",
53
- "rozenite": "1.8.0"
52
+ "@rozenite/vite-plugin": "1.9.0",
53
+ "rozenite": "1.9.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react-native-nitro-fetch": "*",
@@ -7,6 +7,7 @@ import {
7
7
  } from '../../shared/client';
8
8
  import { safeStringify } from '../../utils/safeStringify';
9
9
  import { getStringSizeInBytes } from '../../utils/getStringSizeInBytes';
10
+ import { isJsonContentType } from '../../utils/getContentTypeMimeType';
10
11
  import {
11
12
  isBlob,
12
13
  isArrayBuffer,
@@ -129,7 +130,7 @@ export const getResponseBody = async (
129
130
 
130
131
  if (
131
132
  contentType.startsWith('text/') ||
132
- contentType.startsWith('application/json')
133
+ isJsonContentType(contentType)
133
134
  ) {
134
135
  // It looks like a text blob, let's read it and forward it to the client.
135
136
  return new Promise((resolve) => {
@@ -195,7 +196,7 @@ export const setupRequestOverride = (
195
196
  Object.defineProperty(request, 'responseText', { writable: true });
196
197
 
197
198
  const contentType = getContentType(request);
198
- if (contentType === 'application/json') {
199
+ if (isJsonContentType(contentType)) {
199
200
  request.responseType = 'json';
200
201
  } else if (contentType === 'text/plain') {
201
202
  request.responseType = 'text';
@@ -0,0 +1,9 @@
1
+ import type { NitroModule } from './nitro-network-inspector';
2
+
3
+ export const getNitroModule = (): NitroModule | null => {
4
+ try {
5
+ return require('react-native-nitro-fetch') as NitroModule;
6
+ } catch {
7
+ return null;
8
+ }
9
+ };
@@ -7,6 +7,7 @@ import type {
7
7
  } from '../../shared/client';
8
8
  import type { WebSocketEventMap } from '../../shared/websocket-events';
9
9
  import type { Inspector } from '../inspector';
10
+ import { getNitroModule as loadNitroModule } from './get-nitro-module';
10
11
 
11
12
  type NitroHttpHeader = {
12
13
  key: string;
@@ -62,7 +63,7 @@ type NitroWebSocketEntry = {
62
63
 
63
64
  type NitroInspectorEntry = NitroHttpEntry | NitroWebSocketEntry;
64
65
 
65
- type NitroModule = {
66
+ export type NitroModule = {
66
67
  NetworkInspector: {
67
68
  enable: () => void;
68
69
  disable: () => void;
@@ -107,14 +108,6 @@ export const NITRO_NETWORK_EVENTS: (keyof NitroNetworkEventMap)[] = [
107
108
  'websocket-error',
108
109
  ];
109
110
 
110
- const loadNitroModule = (): NitroModule | null => {
111
- try {
112
- return require('react-native-nitro-fetch') as NitroModule;
113
- } catch {
114
- return null;
115
- }
116
- };
117
-
118
111
  const timestampOrigin =
119
112
  typeof performance !== 'undefined' &&
120
113
  typeof performance.timeOrigin === 'number'
@@ -10,6 +10,7 @@ import { RequestOverride } from '../../shared/client';
10
10
  import { OverrideResponse } from '../components/OverrideResponse';
11
11
  import { Button } from '../components/Button';
12
12
  import { Pencil } from 'lucide-react';
13
+ import { isJsonContentType } from '../../utils/getContentTypeMimeType';
13
14
 
14
15
  export type ResponseTabProps = {
15
16
  selectedRequest: HttpNetworkEntry;
@@ -110,7 +111,7 @@ export const ResponseTab = ({
110
111
  );
111
112
  }
112
113
 
113
- if (type.startsWith('application/json')) {
114
+ if (isJsonContentType(type)) {
114
115
  let bodyContent;
115
116
 
116
117
  try {
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ getContentTypeMime,
4
+ isJsonContentType,
5
+ } from '../getContentTypeMimeType';
6
+
7
+ describe('getContentTypeMimeType', () => {
8
+ it('recognizes application/json content types with parameters', () => {
9
+ expect(isJsonContentType('application/json; charset=utf-8')).toBe(true);
10
+ });
11
+
12
+ it('recognizes RFC 6839 +json content types', () => {
13
+ expect(isJsonContentType('application/vnd.geo+json; charset=utf-8')).toBe(
14
+ true,
15
+ );
16
+ expect(isJsonContentType('Application/LD+JSON')).toBe(true);
17
+ });
18
+
19
+ it('rejects non-json content types', () => {
20
+ expect(isJsonContentType('text/plain')).toBe(false);
21
+ expect(isJsonContentType(undefined)).toBe(false);
22
+ });
23
+
24
+ it('keeps the extracted mime type display-friendly', () => {
25
+ expect(
26
+ getContentTypeMime({
27
+ 'content-type': 'Application/LD+JSON; charset=utf-8',
28
+ }),
29
+ ).toBe('Application/LD+JSON');
30
+ });
31
+ });
@@ -1,6 +1,10 @@
1
1
  import { HttpHeaders } from '../shared/client';
2
2
  import { getHttpHeader } from './getHttpHeader';
3
3
 
4
+ export function normalizeContentType(contentType: string) {
5
+ return contentType.split(';')[0].trim().toLowerCase();
6
+ }
7
+
4
8
  export function getContentTypeMime(headers: HttpHeaders) {
5
9
  const contentType = getHttpHeader(headers, 'content-type');
6
10
 
@@ -15,3 +19,13 @@ export function getContentTypeMime(headers: HttpHeaders) {
15
19
 
16
20
  return actualValue.split(';')[0].trim();
17
21
  }
22
+
23
+ export function isJsonContentType(contentType: string | null | undefined) {
24
+ if (!contentType) {
25
+ return false;
26
+ }
27
+
28
+ const mimeType = normalizeContentType(contentType);
29
+
30
+ return mimeType === 'application/json' || mimeType.endsWith('+json');
31
+ }
package/vite.config.ts CHANGED
@@ -27,6 +27,10 @@ export default defineConfig({
27
27
  return 'event-source';
28
28
  }
29
29
 
30
+ if (id.includes('get-nitro-module.ts')) {
31
+ return 'get-nitro-module';
32
+ }
33
+
30
34
  return undefined;
31
35
  },
32
36
  },