Skip to content

Commit 692b382

Browse files
huntiefacebook-github-bot
authored andcommitted
Support CDP response previews (#52487)
Summary: Continues integration of `NetworkReporter` (jsinspector-modern) on Android, to enable the Network panel in React Native DevTools. NOTE: As with iOS, all changes are gated behind the `enableNetworkEventReporting` and `fuseboxNetworkInspectionEnabled` feature flags. **This diff** Integrates `Network.storeRequestBody` on Android (CDP: [`Network.getResponseBody`](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-getResponseBody) CDP event) to populate the "Preview" and "Response" tabs in the React Native DevTools Network panel. This is integrated with `NetworkingModule.kt` to support synchronously received `text` or `blob` data types, with incremental response support added next in D77927896. Changelog: [Internal] Differential Revision: D77799617
1 parent 8524c13 commit 692b382

File tree

8 files changed

+83
-21
lines changed

8 files changed

+83
-21
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import java.util.HashMap
3535
import java.util.UUID
3636
import okhttp3.MediaType
3737
import okhttp3.RequestBody
38-
import okhttp3.ResponseBody
3938
import okio.ByteString
4039

4140
@ReactModule(name = NativeBlobModuleSpec.NAME)
@@ -72,7 +71,7 @@ public class BlobModule(reactContext: ReactApplicationContext) :
7271
return !isRemote && responseType == "blob"
7372
}
7473

75-
override fun fetch(uri: Uri): WritableMap {
74+
override fun fetch(uri: Uri): Pair<WritableMap, ByteArray> {
7675
val data = getBytesFromUri(uri)
7776

7877
val blob = Arguments.createMap()
@@ -85,7 +84,7 @@ public class BlobModule(reactContext: ReactApplicationContext) :
8584
blob.putString("name", getNameFromUri(uri))
8685
blob.putDouble("lastModified", getLastModifiedFromUri(uri))
8786

88-
return blob
87+
return blob to data
8988
}
9089
}
9190

@@ -119,8 +118,7 @@ public class BlobModule(reactContext: ReactApplicationContext) :
119118
return responseType == "blob"
120119
}
121120

122-
override fun toResponseData(body: ResponseBody): WritableMap {
123-
val data = body.bytes()
121+
override fun toResponseData(data: ByteArray): WritableMap {
124122
val blob = Arguments.createMap()
125123
blob.putString("blobId", store(data))
126124
blob.putInt("offset", 0)

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/InspectorNetworkReporter.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,11 @@ internal object InspectorNetworkReporter {
6464
* - Corresponds to `PerformanceResourceTiming.responseEnd`.
6565
*/
6666
@JvmStatic external fun reportResponseEnd(requestId: Int, encodedDataLength: Long)
67+
68+
/**
69+
* Store response body preview. This is an optional reporting method, and is a no-op if CDP
70+
* debugging is disabled.
71+
*/
72+
@JvmStatic
73+
external fun maybeStoreResponseBody(requestId: Int, body: String, base64Encoded: Boolean)
6774
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkEventUtil.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package com.facebook.react.modules.network
1111

1212
import android.os.Bundle
13+
import android.util.Base64
1314
import com.facebook.react.bridge.Arguments
1415
import com.facebook.react.bridge.ReactApplicationContext
1516
import com.facebook.react.bridge.WritableMap
@@ -92,7 +93,16 @@ internal object NetworkEventUtil {
9293
}
9394

9495
@JvmStatic
95-
fun onDataReceived(reactContext: ReactApplicationContext?, requestId: Int, data: String?) {
96+
fun onDataReceived(
97+
reactContext: ReactApplicationContext?,
98+
requestId: Int,
99+
data: String?,
100+
responseType: String
101+
) {
102+
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
103+
InspectorNetworkReporter.maybeStoreResponseBody(
104+
requestId, data.orEmpty(), responseType == "base64")
105+
}
96106
reactContext?.emitDeviceEvent(
97107
"didReceiveNetworkData",
98108
buildReadableArray {
@@ -102,7 +112,16 @@ internal object NetworkEventUtil {
102112
}
103113

104114
@JvmStatic
105-
fun onDataReceived(reactContext: ReactApplicationContext?, requestId: Int, data: WritableMap?) {
115+
fun onDataReceived(
116+
reactContext: ReactApplicationContext?,
117+
requestId: Int,
118+
data: WritableMap,
119+
rawData: ByteArray
120+
) {
121+
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
122+
InspectorNetworkReporter.maybeStoreResponseBody(
123+
requestId, Base64.encodeToString(rawData, Base64.NO_WRAP), true)
124+
}
106125
reactContext?.emitDeviceEvent(
107126
"didReceiveNetworkData",
108127
Arguments.createArray().apply {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@ public class NetworkingModule(
6060
/** Returns if the handler should be used for an URI. */
6161
public fun supports(uri: Uri, responseType: String): Boolean
6262

63-
/** Fetch the URI and return the JS body payload. */
64-
@Throws(IOException::class) public fun fetch(uri: Uri): WritableMap
63+
/**
64+
* Fetch the URI and return a tuple containing the JS body payload and the raw response body.
65+
*/
66+
@Throws(IOException::class) public fun fetch(uri: Uri): Pair<WritableMap, ByteArray>
6567
}
6668

6769
/** Allows adding custom handling to build the [RequestBody] from the JS body payload. */
@@ -79,7 +81,7 @@ public class NetworkingModule(
7981
public fun supports(responseType: String): Boolean
8082

8183
/** Returns the JS body payload for the [ResponseBody]. */
82-
@Throws(IOException::class) public fun toResponseData(body: ResponseBody): WritableMap
84+
@Throws(IOException::class) public fun toResponseData(data: ByteArray): WritableMap
8385
}
8486

8587
private val client: OkHttpClient
@@ -254,7 +256,7 @@ public class NetworkingModule(
254256
// Check if a handler is registered
255257
for (handler in uriHandlers) {
256258
if (handler.supports(uri, responseType)) {
257-
val res = handler.fetch(uri)
259+
val (res, rawBody) = handler.fetch(uri)
258260
val encodedDataLength = res.toString().toByteArray().size
259261
// fix: UriHandlers which are not using file:// scheme fail in whatwg-fetch at this line
260262
// https://github.com/JakeChampion/fetch/blob/main/fetch.js#L547
@@ -266,7 +268,7 @@ public class NetworkingModule(
266268
.message("OK")
267269
.build()
268270
NetworkEventUtil.onResponseReceived(reactApplicationContext, requestId, url, response)
269-
NetworkEventUtil.onDataReceived(reactApplicationContext, requestId, res)
271+
NetworkEventUtil.onDataReceived(reactApplicationContext, requestId, res, rawBody)
270272
NetworkEventUtil.onRequestSuccess(
271273
reactApplicationContext, requestId, encodedDataLength.toLong())
272274
return
@@ -543,8 +545,10 @@ public class NetworkingModule(
543545
// Check if a handler is registered
544546
for (responseHandler in responseHandlers) {
545547
if (responseHandler.supports(responseType)) {
546-
val res = responseHandler.toResponseData(responseBody)
547-
NetworkEventUtil.onDataReceived(reactApplicationContext, requestId, res)
548+
val responseData = responseBody.bytes()
549+
val res = responseHandler.toResponseData(responseData)
550+
NetworkEventUtil.onDataReceived(
551+
reactApplicationContext, requestId, res, responseData)
548552
NetworkEventUtil.onRequestSuccess(
549553
reactApplicationContext, requestId, responseBody.contentLength())
550554
return
@@ -582,7 +586,7 @@ public class NetworkingModule(
582586
responseString = Base64.encodeToString(responseBody.bytes(), Base64.NO_WRAP)
583587
}
584588
NetworkEventUtil.onDataReceived(
585-
reactApplicationContext, requestId, responseString)
589+
reactApplicationContext, requestId, responseString, responseType)
586590
NetworkEventUtil.onRequestSuccess(
587591
reactApplicationContext, requestId, responseBody.contentLength())
588592
} catch (e: IOException) {

packages/react-native/ReactAndroid/src/main/jni/react/jni/InspectorNetworkReporter.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,23 @@ std::string limitRequestBodySize(std::string requestBody) {
101101
std::to_string(requestId), static_cast<std::int64_t>(encodedDataLength));
102102
}
103103

104+
/* static */ void InspectorNetworkReporter::maybeStoreResponseBody(
105+
jni::alias_ref<jclass> /*unused*/,
106+
jint requestId,
107+
jni::alias_ref<jstring> body,
108+
jboolean base64Encoded) {
109+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
110+
// Debug build: Process response body and report to NetworkReporter
111+
auto& networkReporter = NetworkReporter::getInstance();
112+
if (!networkReporter.isDebuggingEnabled()) {
113+
return;
114+
}
115+
116+
networkReporter.storeResponseBody(
117+
std::to_string(requestId), body->toStdString(), base64Encoded != 0u);
118+
#endif
119+
}
120+
104121
/* static */ void InspectorNetworkReporter::registerNatives() {
105122
javaClassLocal()->registerNatives({
106123
makeNativeMethod(
@@ -112,6 +129,9 @@ std::string limitRequestBodySize(std::string requestBody) {
112129
makeNativeMethod(
113130
"reportConnectionTiming",
114131
InspectorNetworkReporter::reportConnectionTiming),
132+
makeNativeMethod(
133+
"maybeStoreResponseBody",
134+
InspectorNetworkReporter::maybeStoreResponseBody),
115135
});
116136
}
117137

packages/react-native/ReactAndroid/src/main/jni/react/jni/InspectorNetworkReporter.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ class InspectorNetworkReporter
4444
jint requestId,
4545
jlong encodedDataLength);
4646

47+
static void maybeStoreResponseBody(
48+
jni::alias_ref<jclass> /*unused*/,
49+
jint requestId,
50+
jni::alias_ref<jstring> body,
51+
jboolean base64Encoded);
52+
4753
static void registerNatives();
4854

4955
private:

packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkEventUtilTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ class NetworkEventUtilTest {
127127
val requestId = 1
128128
val data = "response data"
129129

130-
NetworkEventUtil.onDataReceived(reactContext, requestId, data)
130+
NetworkEventUtil.onDataReceived(reactContext, requestId, data, "string")
131131

132132
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
133133
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
@@ -147,7 +147,7 @@ class NetworkEventUtilTest {
147147
val requestId = 1
148148
val data: WritableMap = Arguments.createMap().apply { putString("key", "value") }
149149

150-
NetworkEventUtil.onDataReceived(reactContext, requestId, data)
150+
NetworkEventUtil.onDataReceived(reactContext, requestId, data, ByteArray(0))
151151

152152
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
153153
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
@@ -276,8 +276,8 @@ class NetworkEventUtilTest {
276276
NetworkEventUtil.onDataSend(null, 1, 100, 1000)
277277
NetworkEventUtil.onIncrementalDataReceived(null, 1, "data", 100, 1000)
278278
NetworkEventUtil.onDataReceivedProgress(null, 1, 100, 1000)
279-
NetworkEventUtil.onDataReceived(null, 1, "data")
280-
NetworkEventUtil.onDataReceived(null, 1, Arguments.createMap())
279+
NetworkEventUtil.onDataReceived(null, 1, "data", "string")
280+
NetworkEventUtil.onDataReceived(null, 1, Arguments.createMap(), ByteArray(0))
281281
NetworkEventUtil.onRequestError(null, 1, "error", null)
282282
NetworkEventUtil.onRequestSuccess(null, 1, 0)
283283
NetworkEventUtil.onResponseReceived(null, 1, url, response)

packages/react-native/ReactCommon/jsinspector-modern/network/HttpUtils.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "HttpUtils.h"
99

10+
#include <algorithm>
11+
1012
namespace facebook::react::jsinspector_modern {
1113

1214
std::string httpReasonPhrase(uint16_t status) {
@@ -147,8 +149,14 @@ std::string httpReasonPhrase(uint16_t status) {
147149
std::string mimeTypeFromHeaders(const Headers& headers) {
148150
std::string mimeType = "application/octet-stream";
149151

150-
if (headers.find("Content-Type") != headers.end()) {
151-
mimeType = headers.at("Content-Type");
152+
for (const auto& [name, value] : headers) {
153+
std::string lowerName = name;
154+
std::transform(
155+
lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
156+
if (lowerName == "content-type") {
157+
mimeType = value;
158+
break;
159+
}
152160
}
153161

154162
return mimeType;

0 commit comments

Comments
 (0)