Skip to content

Commit 727b5c7

Browse files
huntiefacebook-github-bot
authored andcommitted
Implement core Network CDP events on Android
Summary: Begins integrating `NetworkReporter` (jsinspector-modern) on Android, to enable the Network panel in React Native DevTools. Since the larger lift of initial setup and the C++ subsystem has been done for iOS, this will be a lighter stack of changes solely setting up necessary integration points in the Android Networking stack. NOTE: As with iOS, all changes are gated behind the `enableNetworkEventReporting` and `fuseboxNetworkInspectionEnabled` feature flags. **This diff** Initially integrates the `NetworkReporter` methods corresponding to the `Network.requestWillBeSent`, `Network.requestWillBeSentExtraInfo`, `Network.responseReceived`, `Network.loadingFinished` CDP events, which are sufficient for populating a minimally rendered Network request list. - Create JNI `InspectorNetworkReporter` helper class (may also become the later public API for 3P reporting into the `Network` domain). - Renames `ResponseUtil.kt` as `NetworkEventUtil.kt`. Changelog: [Internal] Differential Revision: D71897099
1 parent 68650ba commit 727b5c7

File tree

7 files changed

+394
-72
lines changed

7 files changed

+394
-72
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.modules.network
9+
10+
import com.facebook.proguard.annotations.DoNotStripAny
11+
12+
/**
13+
* [Experimental] An interface for reporting network events to the modern debugger server and Web
14+
* Performance APIs.
15+
*
16+
* In a production (non dev or profiling) build, CDP reporting is disabled.
17+
*
18+
* This is a helper class wrapping `facebook::react::jsinspector_modern::NetworkReporter`.
19+
*/
20+
@DoNotStripAny
21+
internal object InspectorNetworkReporter {
22+
/**
23+
* Report a network request that is about to be sent.
24+
* - Corresponds to `Network.requestWillBeSent` in CDP.
25+
* - Corresponds to `PerformanceResourceTiming.requestStart` (specifically, marking when the
26+
* native request was initiated).
27+
*/
28+
@JvmStatic
29+
external fun reportRequestStart(
30+
requestId: Int,
31+
requestUrl: String,
32+
requestMethod: String,
33+
requestHeaders: Map<String, String>,
34+
requestBody: String,
35+
encodedDataLength: Long
36+
)
37+
38+
/**
39+
* Report detailed timing info, such as DNS lookup, when a request has started.
40+
* - Corresponds to `Network.requestWillBeSentExtraInfo` in CDP.
41+
* - Corresponds to `PerformanceResourceTiming.domainLookupStart`,
42+
* `PerformanceResourceTiming.connectStart`.
43+
*/
44+
@JvmStatic external fun reportConnectionTiming(requestId: Int, headers: Map<String, String>)
45+
46+
/**
47+
* Report when HTTP response headers have been received, corresponding to when the first byte of
48+
* the response is available.
49+
* - Corresponds to `Network.responseReceived` in CDP.
50+
* - Corresponds to `PerformanceResourceTiming.responseStart`.
51+
*/
52+
@JvmStatic
53+
external fun reportResponseStart(
54+
requestId: Int,
55+
responseUrl: String,
56+
responseStatus: Int,
57+
responseHeaders: Map<String, String>,
58+
expectedDataLength: Long
59+
)
60+
61+
/**
62+
* Report when a network request is complete and we are no longer receiving response data.
63+
* - Corresponds to `Network.loadingFinished` in CDP.
64+
* - Corresponds to `PerformanceResourceTiming.responseEnd`.
65+
*/
66+
@JvmStatic external fun reportResponseEnd(requestId: Int, encodedDataLength: Long)
67+
}

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

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,32 @@ import com.facebook.react.bridge.Arguments
1111
import com.facebook.react.bridge.ReactApplicationContext
1212
import com.facebook.react.bridge.WritableMap
1313
import com.facebook.react.bridge.buildReadableArray
14+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
1415
import java.net.SocketTimeoutException
16+
import okhttp3.Headers
17+
import okhttp3.Request
18+
import okhttp3.Response
19+
20+
/**
21+
* Utility class for reporting network lifecycle events to JavaScript and InspectorNetworkReporter.
22+
*/
23+
internal object NetworkEventUtil {
24+
@JvmStatic
25+
fun onCreateRequest(requestId: Int, request: Request) {
26+
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
27+
val headersMap = okHttpHeadersToMap(request.headers())
28+
InspectorNetworkReporter.reportRequestStart(
29+
requestId,
30+
request.url().toString(),
31+
request.method(),
32+
headersMap,
33+
request.body()?.toString() ?: "",
34+
request.body()?.contentLength() ?: 0,
35+
)
36+
InspectorNetworkReporter.reportConnectionTiming(requestId, headersMap)
37+
}
38+
}
1539

16-
/** Util methods to send network responses to JS. */
17-
internal object ResponseUtil {
1840
@JvmStatic
1941
fun onDataSend(
2042
reactContext: ReactApplicationContext?,
@@ -104,7 +126,14 @@ internal object ResponseUtil {
104126
}
105127

106128
@JvmStatic
107-
fun onRequestSuccess(reactContext: ReactApplicationContext?, requestId: Int) {
129+
fun onRequestSuccess(
130+
reactContext: ReactApplicationContext?,
131+
requestId: Int,
132+
encodedDataLength: Long
133+
) {
134+
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
135+
InspectorNetworkReporter.reportResponseEnd(requestId, encodedDataLength)
136+
}
108137
reactContext?.emitDeviceEvent(
109138
"didCompleteNetworkResponse",
110139
buildReadableArray {
@@ -117,17 +146,72 @@ internal object ResponseUtil {
117146
fun onResponseReceived(
118147
reactContext: ReactApplicationContext?,
119148
requestId: Int,
120-
statusCode: Int,
121-
headers: WritableMap?,
122-
url: String?
149+
response: Response,
123150
) {
151+
val responseUrl = response.request().url().toString()
152+
val headersMap = okHttpHeadersToMap(response.headers())
153+
154+
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
155+
InspectorNetworkReporter.reportResponseStart(
156+
requestId,
157+
responseUrl,
158+
response.code(),
159+
headersMap,
160+
response.body()?.contentLength() ?: 0,
161+
)
162+
}
124163
reactContext?.emitDeviceEvent(
125164
"didReceiveNetworkResponse",
126165
Arguments.createArray().apply {
127166
pushInt(requestId)
128-
pushInt(statusCode)
129-
pushMap(headers)
130-
pushString(url)
167+
pushInt(response.code())
168+
pushMap(Arguments.makeNativeMap(headersMap))
169+
170+
pushString(responseUrl)
131171
})
132172
}
173+
174+
@Deprecated("Compatibility overload")
175+
@JvmStatic
176+
fun onResponseReceived(
177+
reactContext: ReactApplicationContext?,
178+
requestId: Int,
179+
statusCode: Int,
180+
headers: WritableMap?,
181+
url: String?
182+
) {
183+
val headersBuilder = Headers.Builder()
184+
headers?.let { map ->
185+
val iterator = map.keySetIterator()
186+
while (iterator.hasNextKey()) {
187+
val key = iterator.nextKey()
188+
val value = map.getString(key)
189+
if (value != null) {
190+
headersBuilder.add(key, value)
191+
}
192+
}
193+
}
194+
onResponseReceived(
195+
reactContext,
196+
requestId,
197+
Response.Builder()
198+
.code(statusCode)
199+
.request(Request.Builder().url(url.orEmpty()).build())
200+
.headers(headersBuilder.build())
201+
.build())
202+
}
203+
204+
private fun okHttpHeadersToMap(headers: Headers): Map<String, String> {
205+
val responseHeaders = mutableMapOf<String, String>()
206+
for (i in 0 until headers.size()) {
207+
val headerName = headers.name(i)
208+
// multiple values for the same header
209+
if (responseHeaders.containsKey(headerName)) {
210+
responseHeaders[headerName] = responseHeaders[headerName] + ", " + headers.value(i)
211+
} else {
212+
responseHeaders[headerName] = headers.value(i)
213+
}
214+
}
215+
return responseHeaders
216+
}
133217
}

0 commit comments

Comments
 (0)