@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 +22 -0
- package/dist/devtools/App.html +1 -1
- package/dist/devtools/assets/{App-B3xlUjs6.js → App-hSoryVpJ.js} +11 -1
- package/dist/react-native/chunks/boot-recording.cjs +19 -15
- package/dist/react-native/chunks/boot-recording.js +19 -15
- package/dist/react-native/chunks/get-nitro-module.cjs +9 -0
- package/dist/react-native/chunks/get-nitro-module.js +10 -0
- package/dist/react-native/index.d.ts +2 -2
- package/dist/rozenite.json +1 -1
- package/package.json +6 -6
- package/src/react-native/http/http-utils.ts +3 -2
- package/src/react-native/nitro-fetch/get-nitro-module.ts +9 -0
- package/src/react-native/nitro-fetch/nitro-network-inspector.ts +2 -9
- package/src/ui/tabs/ResponseTab.tsx +2 -1
- package/src/utils/__tests__/getContentTypeMimeType.test.ts +31 -0
- package/src/utils/getContentTypeMimeType.ts +14 -0
- package/vite.config.ts +4 -0
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
|
package/dist/devtools/App.html
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
}
|
|
@@ -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.
|
package/dist/rozenite.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@rozenite/network-activity-plugin","version":"1.
|
|
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.
|
|
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.
|
|
20
|
-
"@rozenite/agent-bridge": "1.
|
|
21
|
-
"@rozenite/plugin-bridge": "1.
|
|
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.
|
|
53
|
-
"rozenite": "1.
|
|
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
|
|
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
|
|
199
|
+
if (isJsonContentType(contentType)) {
|
|
199
200
|
request.responseType = 'json';
|
|
200
201
|
} else if (contentType === 'text/plain') {
|
|
201
202
|
request.responseType = 'text';
|
|
@@ -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
|
|
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
|
+
}
|