Skip to content

Commit 989f5ff

Browse files
huntiefacebook-github-bot
authored andcommitted
Implement core Network CDP events on Android (#52485)
Summary: Pull Request resolved: #52485 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 2e55904 commit 989f5ff

File tree

10 files changed

+591
-217
lines changed

10 files changed

+591
-217
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+
requestUrl: 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+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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 android.os.Bundle
11+
import com.facebook.react.bridge.Arguments
12+
import com.facebook.react.bridge.ReactApplicationContext
13+
import com.facebook.react.bridge.WritableMap
14+
import com.facebook.react.bridge.buildReadableArray
15+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
16+
import java.net.SocketTimeoutException
17+
import okhttp3.Headers
18+
import okhttp3.Protocol
19+
import okhttp3.Request
20+
import okhttp3.Response
21+
22+
/**
23+
* Utility class for reporting network lifecycle events to JavaScript and InspectorNetworkReporter.
24+
*/
25+
internal object NetworkEventUtil {
26+
@JvmStatic
27+
fun onCreateRequest(requestId: Int, request: Request) {
28+
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
29+
val headersMap = okHttpHeadersToMap(request.headers())
30+
InspectorNetworkReporter.reportRequestStart(
31+
requestId,
32+
request.url().toString(),
33+
request.method(),
34+
headersMap,
35+
request.body()?.toString() ?: "",
36+
request.body()?.contentLength() ?: 0,
37+
)
38+
InspectorNetworkReporter.reportConnectionTiming(requestId, headersMap)
39+
}
40+
}
41+
42+
@JvmStatic
43+
fun onDataSend(
44+
reactContext: ReactApplicationContext?,
45+
requestId: Int,
46+
progress: Long,
47+
total: Long
48+
) {
49+
reactContext?.emitDeviceEvent(
50+
"didSendNetworkData",
51+
buildReadableArray {
52+
add(requestId)
53+
add(progress.toInt())
54+
add(total.toInt())
55+
})
56+
}
57+
58+
@JvmStatic
59+
fun onIncrementalDataReceived(
60+
reactContext: ReactApplicationContext?,
61+
requestId: Int,
62+
data: String?,
63+
progress: Long,
64+
total: Long
65+
) {
66+
reactContext?.emitDeviceEvent(
67+
"didReceiveNetworkIncrementalData",
68+
buildReadableArray {
69+
add(requestId)
70+
add(data)
71+
add(progress.toInt())
72+
add(total.toInt())
73+
})
74+
}
75+
76+
@JvmStatic
77+
fun onDataReceivedProgress(
78+
reactContext: ReactApplicationContext?,
79+
requestId: Int,
80+
progress: Long,
81+
total: Long
82+
) {
83+
reactContext?.emitDeviceEvent(
84+
"didReceiveNetworkDataProgress",
85+
buildReadableArray {
86+
add(requestId)
87+
add(progress.toInt())
88+
add(total.toInt())
89+
})
90+
}
91+
92+
@JvmStatic
93+
fun onDataReceived(reactContext: ReactApplicationContext?, requestId: Int, data: String?) {
94+
reactContext?.emitDeviceEvent(
95+
"didReceiveNetworkData",
96+
buildReadableArray {
97+
add(requestId)
98+
add(data)
99+
})
100+
}
101+
102+
@JvmStatic
103+
fun onDataReceived(reactContext: ReactApplicationContext?, requestId: Int, data: WritableMap?) {
104+
reactContext?.emitDeviceEvent(
105+
"didReceiveNetworkData",
106+
Arguments.createArray().apply {
107+
pushInt(requestId)
108+
pushMap(data)
109+
})
110+
}
111+
112+
@JvmStatic
113+
fun onRequestError(
114+
reactContext: ReactApplicationContext?,
115+
requestId: Int,
116+
error: String?,
117+
e: Throwable?
118+
) {
119+
reactContext?.emitDeviceEvent(
120+
"didCompleteNetworkResponse",
121+
buildReadableArray {
122+
add(requestId)
123+
add(error)
124+
if (e?.javaClass == SocketTimeoutException::class.java) {
125+
add(true) // last argument is a time out boolean
126+
}
127+
})
128+
}
129+
130+
@JvmStatic
131+
fun onRequestSuccess(
132+
reactContext: ReactApplicationContext?,
133+
requestId: Int,
134+
encodedDataLength: Long
135+
) {
136+
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
137+
InspectorNetworkReporter.reportResponseEnd(requestId, encodedDataLength)
138+
}
139+
reactContext?.emitDeviceEvent(
140+
"didCompleteNetworkResponse",
141+
buildReadableArray {
142+
add(requestId)
143+
addNull()
144+
})
145+
}
146+
147+
@JvmStatic
148+
fun onResponseReceived(
149+
reactContext: ReactApplicationContext?,
150+
requestId: Int,
151+
requestUrl: String?,
152+
response: Response,
153+
) {
154+
val headersMap = okHttpHeadersToMap(response.headers())
155+
val headersBundle = Bundle()
156+
for ((headerName, headerValue) in headersMap) {
157+
headersBundle.putString(headerName, headerValue)
158+
}
159+
160+
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
161+
InspectorNetworkReporter.reportResponseStart(
162+
requestId,
163+
requestUrl.orEmpty(),
164+
response.code(),
165+
headersMap,
166+
response.body()?.contentLength() ?: 0,
167+
)
168+
}
169+
reactContext?.emitDeviceEvent(
170+
"didReceiveNetworkResponse",
171+
Arguments.createArray().apply {
172+
pushInt(requestId)
173+
pushInt(response.code())
174+
pushMap(Arguments.fromBundle(headersBundle))
175+
pushString(requestUrl)
176+
})
177+
}
178+
179+
@Deprecated("Compatibility overload")
180+
@JvmStatic
181+
fun onResponseReceived(
182+
reactContext: ReactApplicationContext?,
183+
requestId: Int,
184+
statusCode: Int,
185+
headers: WritableMap?,
186+
url: String?
187+
) {
188+
val headersBuilder = Headers.Builder()
189+
headers?.let { map ->
190+
val iterator = map.keySetIterator()
191+
while (iterator.hasNextKey()) {
192+
val key = iterator.nextKey()
193+
val value = map.getString(key)
194+
if (value != null) {
195+
headersBuilder.add(key, value)
196+
}
197+
}
198+
}
199+
onResponseReceived(
200+
reactContext,
201+
requestId,
202+
url,
203+
Response.Builder()
204+
.protocol(Protocol.HTTP_1_1)
205+
.request(Request.Builder().url(url.orEmpty()).build())
206+
.headers(headersBuilder.build())
207+
.code(statusCode)
208+
.message("")
209+
.build())
210+
}
211+
212+
private fun okHttpHeadersToMap(headers: Headers): Map<String, String> {
213+
val responseHeaders = mutableMapOf<String, String>()
214+
for (i in 0 until headers.size()) {
215+
val headerName = headers.name(i)
216+
// multiple values for the same header
217+
if (responseHeaders.containsKey(headerName)) {
218+
responseHeaders[headerName] = responseHeaders[headerName] + ", " + headers.value(i)
219+
} else {
220+
responseHeaders[headerName] = headers.value(i)
221+
}
222+
}
223+
return responseHeaders
224+
}
225+
}

0 commit comments

Comments
 (0)