Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public class BlobModule(reactContext: ReactApplicationContext) :
}
}

private val networkingUriHandler =
internal val networkingUriHandler =
object : NetworkingModule.UriHandler {
override fun supports(uri: Uri, responseType: String): Boolean {
val scheme = uri.scheme
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import java.net.SocketTimeoutException
import okhttp3.Headers
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response

/**
* Utility class for reporting network lifecycle events to JavaScript and InspectorNetworkReporter.
Expand Down Expand Up @@ -229,34 +227,36 @@ internal object NetworkEventUtil {
requestId: Int,
devToolsRequestId: String,
requestUrl: String?,
response: Response,
statusCode: Int,
headers: Map<String, String>,
contentLength: Long,
) {
val headersMap = okHttpHeadersToMap(response.headers())
val headersBundle = Bundle()
for ((headerName, headerValue) in headersMap) {
for ((headerName, headerValue) in headers) {
headersBundle.putString(headerName, headerValue)
}

if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
InspectorNetworkReporter.reportResponseStart(
devToolsRequestId,
requestUrl.orEmpty(),
response.code(),
headersMap,
response.body()?.contentLength() ?: 0,
statusCode,
headers,
contentLength,
)
}
reactContext?.emitDeviceEvent(
"didReceiveNetworkResponse",
Arguments.createArray().apply {
pushInt(requestId)
pushInt(response.code())
pushInt(statusCode)
pushMap(Arguments.fromBundle(headersBundle))
pushString(requestUrl)
},
)
}

// TODO(#55747): Remove this overload once fbsource no longer depends on it.
@Deprecated("Compatibility overload")
@JvmStatic
fun onResponseReceived(
Expand All @@ -267,14 +267,15 @@ internal object NetworkEventUtil {
headers: WritableMap?,
url: String?,
) {
val headersBuilder = Headers.Builder()
headers?.let { map ->
val iterator = map.keySetIterator()
while (iterator.hasNextKey()) {
val key = iterator.nextKey()
val value = map.getString(key)
if (value != null) {
headersBuilder.add(key, value)
val responseHeaders = buildMap<String, String> {
headers?.let { map ->
val iterator = map.keySetIterator()
while (iterator.hasNextKey()) {
val key = iterator.nextKey()
val value = map.getString(key)
if (value != null) {
put(key, value)
}
}
}
}
Expand All @@ -283,17 +284,12 @@ internal object NetworkEventUtil {
requestId,
devToolsRequestId,
url,
Response.Builder()
.protocol(Protocol.HTTP_1_1)
.request(Request.Builder().url(url.orEmpty()).build())
.headers(headersBuilder.build())
.code(statusCode)
.message("")
.build(),
)
statusCode,
responseHeaders,
0)
}

private fun okHttpHeadersToMap(headers: Headers): Map<String, String> {
internal fun okHttpHeadersToMap(headers: Headers): Map<String, String> {
val responseHeaders = mutableMapOf<String, String>()
for (i in 0 until headers.size()) {
val headerName = headers.name(i)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import okhttp3.JavaNetCookieJar
import okhttp3.MediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
Expand Down Expand Up @@ -310,21 +309,14 @@ public class NetworkingModule(
if (handler.supports(uri, responseType)) {
val (res, rawBody) = handler.fetch(uri)
val encodedDataLength = res.toString().toByteArray().size
// fix: UriHandlers which are not using file:// scheme fail in whatwg-fetch at this line
// https://github.com/JakeChampion/fetch/blob/main/fetch.js#L547
val response =
Response.Builder()
.protocol(Protocol.HTTP_1_1)
.request(Request.Builder().url(url.orEmpty()).build())
.code(200)
.message("OK")
.build()
NetworkEventUtil.onResponseReceived(
reactApplicationContext,
requestId,
devToolsRequestId,
url,
response,
200,
emptyMap(),
encodedDataLength.toLong(),
)
NetworkEventUtil.onDataReceived(
reactApplicationContext,
Expand Down Expand Up @@ -645,7 +637,9 @@ public class NetworkingModule(
requestId,
devToolsRequestId,
response.request().url().toString(),
response,
response.code(),
NetworkEventUtil.okHttpHeadersToMap(response.headers()),
response.body()?.contentLength() ?: 0L,
)

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ package com.facebook.react.modules.blob
import android.net.Uri
import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactTestHelper
import com.facebook.testutils.shadows.ShadowArguments
import java.io.ByteArrayInputStream
import java.nio.ByteBuffer
import java.util.UUID
import kotlin.random.Random
Expand All @@ -20,21 +23,24 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
@Config(manifest = Config.NONE, shadows = [ShadowArguments::class])
class BlobModuleTest {
private lateinit var bytes: ByteArray
private lateinit var blobId: String
private lateinit var context: ReactApplicationContext
private lateinit var blobModule: BlobModule

@Before
fun prepareModules() {
bytes = ByteArray(120)
Random.Default.nextBytes(bytes)

blobModule = BlobModule(ReactTestHelper.createCatalystContextForTest())
context = ReactTestHelper.createCatalystContextForTest()
blobModule = BlobModule(context)
blobId = blobModule.store(bytes)
}

Expand Down Expand Up @@ -136,4 +142,76 @@ class BlobModuleTest {

assertThat(blobModule.resolve(blobId, 0, bytes.size)).isNull()
}

@Test
fun testUriHandlerSupportsContentUri() {
val handler = blobModule.networkingUriHandler
val uri = Uri.parse("content://com.example.provider/blob/123")
assertThat(handler.supports(uri, "blob")).isTrue()
}

@Test
fun testUriHandlerDoesNotSupportContentUriWithNonBlobResponseType() {
val handler = blobModule.networkingUriHandler
val uri = Uri.parse("content://com.example.provider/blob/123")
assertThat(handler.supports(uri, "text")).isFalse()
}

@Test
fun testUriHandlerDoesNotSupportHttpUri() {
val handler = blobModule.networkingUriHandler
val uri = Uri.parse("http://example.com/blob/123")
assertThat(handler.supports(uri, "blob")).isFalse()
}

@Test
fun testUriHandlerDoesNotSupportHttpsUri() {
val handler = blobModule.networkingUriHandler
val uri = Uri.parse("https://example.com/blob/123")
assertThat(handler.supports(uri, "blob")).isFalse()
}

@Test
fun testUriHandlerSupportsFileUriWithBlobResponseType() {
val handler = blobModule.networkingUriHandler
val uri = Uri.parse("file:///storage/emulated/0/Download/test.pdf")
assertThat(handler.supports(uri, "blob")).isTrue()
}

@Test
fun testUriHandlerFetchesContentUri() {
val testData = "Hello from content provider!".toByteArray()
val contentUri = Uri.parse("content://com.example.provider/files/test.txt")

val shadowResolver = shadowOf(context.contentResolver)
shadowResolver.registerInputStream(contentUri, ByteArrayInputStream(testData))

val handler = blobModule.networkingUriHandler
assertThat(handler.supports(contentUri, "blob")).isTrue()

val (blob, data) = handler.fetch(contentUri)
assertThat(data).isEqualTo(testData)
assertThat(blob.getInt("offset")).isEqualTo(0)
assertThat(blob.getInt("size")).isEqualTo(testData.size)
assertThat(blob.getString("blobId")).isNotEmpty()
}

@Test
fun testUriHandlerFetchesFileUri() {
val testData = "Hello from a local file!".toByteArray()
val fileUri = Uri.parse("file:///storage/emulated/0/Download/test.txt")

val shadowResolver = shadowOf(context.contentResolver)
shadowResolver.registerInputStream(fileUri, ByteArrayInputStream(testData))

val handler = blobModule.networkingUriHandler

assertThat(handler.supports(fileUri, "blob")).isTrue()

val (blob, data) = handler.fetch(fileUri)
assertThat(data).isEqualTo(testData)
assertThat(blob.getInt("offset")).isEqualTo(0)
assertThat(blob.getInt("size")).isEqualTo(testData.size)
assertThat(blob.getString("blobId")).isNotEmpty()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
import com.facebook.testutils.shadows.ShadowArguments
import java.net.SocketTimeoutException
import okhttp3.Headers
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
Expand Down Expand Up @@ -265,24 +261,17 @@ class NetworkEventUtilTest {
fun testOnResponseReceived() {
val requestId = 1
val statusCode = 200
val headers = Headers.Builder().add("Content-Type", "application/json").build()
val headersMap = mapOf("Content-Type" to "application/json")
val url = "http://example.com"

val request = Request.Builder().url(url).build()
val response =
Response.Builder()
.protocol(Protocol.HTTP_1_1)
.request(request)
.headers(headers)
.code(statusCode)
.message("OK")
.build()
NetworkEventUtil.onResponseReceived(
reactContext,
requestId,
"test_devtools_request_$requestId",
url,
response,
statusCode,
headersMap,
0L,
)

val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
Expand All @@ -306,15 +295,6 @@ class NetworkEventUtilTest {
@Test
fun testNullReactContext() {
val url = "http://example.com"
val request = Request.Builder().url(url).build()
val response =
Response.Builder()
.protocol(Protocol.HTTP_1_1)
.request(request)
.headers(Headers.Builder().build())
.code(200)
.message("OK")
.build()

NetworkEventUtil.onDataSend(null, 1, 100, 1000)
NetworkEventUtil.onIncrementalDataReceived(
Expand All @@ -336,7 +316,7 @@ class NetworkEventUtilTest {
)
NetworkEventUtil.onRequestError(null, 1, "test_devtools_request_1", "error", null)
NetworkEventUtil.onRequestSuccess(null, 1, "test_devtools_request_1", 0)
NetworkEventUtil.onResponseReceived(null, 1, "test_devtools_request_1", url, response)
NetworkEventUtil.onResponseReceived(null, 1, "test_devtools_request_1", url, 200, emptyMap(), 0L)

verify(reactContext, never()).emitDeviceEvent(any<String>(), any())
}
Expand Down
Loading