diff --git a/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetch.kt b/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetch.kt index 89170c6..8d74810 100644 --- a/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetch.kt +++ b/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetch.kt @@ -44,6 +44,7 @@ class NitroFetch : HybridNitroFetchSpec() { val nativeProvider = providers.firstOrNull { it.name.contains("Native", ignoreCase = true) } val cacheDir = File(app.cacheDir, "nitrofetch_cronet_cache").apply { mkdirs() } + val builder = (nativeProvider?.createBuilder() ?: CronetEngine.Builder(app)) .enableHttp2(true) .enableQuic(true) @@ -51,6 +52,12 @@ class NitroFetch : HybridNitroFetchSpec() { .setStoragePath(cacheDir.absolutePath) .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, 50 * 1024 * 1024) .setUserAgent("NitroFetch/0.1") + .enableNetworkQualityEstimator(true) + + // Note: NQE experimental options can be configured via setExperimentalOptions() if needed: + // val nqeOptions = """{"NetworkQualityEstimator":{"effective_connection_type_algorithm":"TransportRTTAndDownstreamThroughput"}}""" + // builder.setExperimentalOptions(nqeOptions) + // This requires Cronet 119+ or embedded Cronet with the method available // --- Optional debugging knobs (uncomment temporarily) --- diff --git a/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt b/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt index 9f3d63a..2b751da 100644 --- a/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +++ b/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt @@ -252,5 +252,50 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E return promise } + override fun getNetworkQualityEstimate(): NetworkQualityEstimate { + try { + // Note: NQE values are based on observations from actual network requests. + // Values update as the app makes network calls and Cronet learns about network conditions. + + // Get RTT estimates (in milliseconds, -1 if unknown) + val httpRttMs = engine.httpRttMs + val transportRttMs = engine.transportRttMs + + // Get throughput estimates (in kbps, -1 if unknown) + val downstreamKbps = engine.downstreamThroughputKbps + + // Get effective connection type + val effectiveConnectionType = when (engine.effectiveConnectionType) { + CronetEngine.EFFECTIVE_CONNECTION_TYPE_UNKNOWN -> "unknown" + CronetEngine.EFFECTIVE_CONNECTION_TYPE_OFFLINE -> "offline" + CronetEngine.EFFECTIVE_CONNECTION_TYPE_SLOW_2G -> "slow-2G" + CronetEngine.EFFECTIVE_CONNECTION_TYPE_2G -> "2G" + CronetEngine.EFFECTIVE_CONNECTION_TYPE_3G -> "3G" + CronetEngine.EFFECTIVE_CONNECTION_TYPE_4G -> "4G" + else -> "unknown" + } + + Log.i("NitroFetchClient", "NQESnapshot - httpRtt: ${httpRttMs}ms, transportRtt: ${transportRttMs}ms, downstream: ${downstreamKbps}kbps, effectiveType: ${effectiveConnectionType}") + + return NetworkQualityEstimate( + downstreamThroughputKbps = if (downstreamKbps != -1) downstreamKbps.toDouble() else null, + upstreamThroughputKbps = null, // Cronet doesn't provide upstream throughput + httpRttMs = if (httpRttMs != -1) httpRttMs.toDouble() else null, + transportRttMs = if (transportRttMs != -1) transportRttMs.toDouble() else null, + effectiveConnectionType = effectiveConnectionType + ) + } catch (e: Exception) { + Log.e("NitroFetchClient", "Error getting network quality estimate", e) + // Return empty estimate on error + return NetworkQualityEstimate( + downstreamThroughputKbps = null, + upstreamThroughputKbps = null, + httpRttMs = null, + transportRttMs = null, + effectiveConnectionType = "unknown" + ) + } + } + } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index e52777e..17cd235 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -8,7 +8,7 @@ PODS: - hermes-engine (0.81.0): - hermes-engine/Pre-built (= 0.81.0) - hermes-engine/Pre-built (0.81.0) - - NitroFetch (0.1.1): + - NitroFetch (0.1.3): - boost - DoubleConversion - fast_float @@ -2644,7 +2644,7 @@ SPEC CHECKSUMS: fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: e7491a2038f2618c8cd444ed411a6deb350a3742 - NitroFetch: 3653fb32c838befe759159a7b22e83a89344b191 + NitroFetch: d99e3c3dad9f70317525c4fad230dff507857720 NitroModules: 33aca4acd8fd5c2a29dc99976680cd99e7263573 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: 0735ab4f6b3ec93a7f98187b5da74d7916e2cf4c diff --git a/example/src/App.tsx b/example/src/App.tsx index 8b2470f..c5490b1 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -14,6 +14,7 @@ import { prefetch, prefetchOnAppStart, removeAllFromAutoprefetch, + getNetworkQualityEstimate, } from 'react-native-nitro-fetch'; type Row = { @@ -185,6 +186,12 @@ export default function App() { const loadPrices = React.useCallback(async () => { console.log('Loading crypto prices from coingecko start'); + try{ + const networkQuality = await getNetworkQualityEstimate(); + console.log('Network quality:', networkQuality); + } catch (e: any) { + console.error('Error getting network quality:', e); + } const ids = [ 'bitcoin', 'ethereum', diff --git a/ios/NitroFetchClient.swift b/ios/NitroFetchClient.swift index 91c731e..1a6534e 100644 --- a/ios/NitroFetchClient.swift +++ b/ios/NitroFetchClient.swift @@ -29,6 +29,20 @@ final class NitroFetchClient: HybridNitroFetchClientSpec { return promise } + func getNetworkQualityEstimate() throws -> NetworkQualityEstimate { + // NQE is not supported on iOS yet - requires Cronet integration + NSLog("[NitroFetch] Network Quality Estimator (NQE) is not supported on iOS yet. It will be available once iOS uses Cronet.") + + // Return empty estimate + return NetworkQualityEstimate( + downstreamThroughputKbps: nil, + upstreamThroughputKbps: nil, + httpRttMs: nil, + transportRttMs: nil, + effectiveConnectionType: "unknown" + ) + } + // Shared URLSession for static operations private static let session: URLSession = { let config = URLSessionConfiguration.default diff --git a/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp b/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp index 3351207..df4485c 100644 --- a/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp @@ -11,6 +11,8 @@ namespace margelo::nitro::nitrofetch { struct NitroResponse; } // Forward declaration of `NitroHeader` to properly resolve imports. namespace margelo::nitro::nitrofetch { struct NitroHeader; } +// Forward declaration of `NetworkQualityEstimate` to properly resolve imports. +namespace margelo::nitro::nitrofetch { struct NetworkQualityEstimate; } // Forward declaration of `NitroRequest` to properly resolve imports. namespace margelo::nitro::nitrofetch { struct NitroRequest; } // Forward declaration of `NitroRequestMethod` to properly resolve imports. @@ -25,6 +27,8 @@ namespace margelo::nitro::nitrofetch { enum class NitroRequestMethod; } #include #include "JNitroHeader.hpp" #include +#include "NetworkQualityEstimate.hpp" +#include "JNetworkQualityEstimate.hpp" #include "NitroRequest.hpp" #include "JNitroRequest.hpp" #include "NitroRequestMethod.hpp" @@ -87,5 +91,10 @@ namespace margelo::nitro::nitrofetch { return __promise; }(); } + NetworkQualityEstimate JHybridNitroFetchClientSpec::getNetworkQualityEstimate() { + static const auto method = javaClassStatic()->getMethod()>("getNetworkQualityEstimate"); + auto __result = method(_javaPart); + return __result->toCpp(); + } } // namespace margelo::nitro::nitrofetch diff --git a/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp b/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp index b06ff39..c65b1f6 100644 --- a/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp +++ b/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp @@ -55,6 +55,7 @@ namespace margelo::nitro::nitrofetch { // Methods std::shared_ptr> request(const NitroRequest& req) override; std::shared_ptr> prefetch(const NitroRequest& req) override; + NetworkQualityEstimate getNetworkQualityEstimate() override; private: friend HybridBase; diff --git a/nitrogen/generated/android/c++/JNetworkQualityEstimate.hpp b/nitrogen/generated/android/c++/JNetworkQualityEstimate.hpp new file mode 100644 index 0000000..b9cd94b --- /dev/null +++ b/nitrogen/generated/android/c++/JNetworkQualityEstimate.hpp @@ -0,0 +1,70 @@ +/// +/// JNetworkQualityEstimate.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "NetworkQualityEstimate.hpp" + +#include +#include + +namespace margelo::nitro::nitrofetch { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "NetworkQualityEstimate" and the the Kotlin data class "NetworkQualityEstimate". + */ + struct JNetworkQualityEstimate final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrofetch/NetworkQualityEstimate;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct NetworkQualityEstimate by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + NetworkQualityEstimate toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldDownstreamThroughputKbps = clazz->getField("downstreamThroughputKbps"); + jni::local_ref downstreamThroughputKbps = this->getFieldValue(fieldDownstreamThroughputKbps); + static const auto fieldUpstreamThroughputKbps = clazz->getField("upstreamThroughputKbps"); + jni::local_ref upstreamThroughputKbps = this->getFieldValue(fieldUpstreamThroughputKbps); + static const auto fieldHttpRttMs = clazz->getField("httpRttMs"); + jni::local_ref httpRttMs = this->getFieldValue(fieldHttpRttMs); + static const auto fieldTransportRttMs = clazz->getField("transportRttMs"); + jni::local_ref transportRttMs = this->getFieldValue(fieldTransportRttMs); + static const auto fieldEffectiveConnectionType = clazz->getField("effectiveConnectionType"); + jni::local_ref effectiveConnectionType = this->getFieldValue(fieldEffectiveConnectionType); + return NetworkQualityEstimate( + downstreamThroughputKbps != nullptr ? std::make_optional(downstreamThroughputKbps->value()) : std::nullopt, + upstreamThroughputKbps != nullptr ? std::make_optional(upstreamThroughputKbps->value()) : std::nullopt, + httpRttMs != nullptr ? std::make_optional(httpRttMs->value()) : std::nullopt, + transportRttMs != nullptr ? std::make_optional(transportRttMs->value()) : std::nullopt, + effectiveConnectionType != nullptr ? std::make_optional(effectiveConnectionType->toStdString()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const NetworkQualityEstimate& value) { + return newInstance( + value.downstreamThroughputKbps.has_value() ? jni::JDouble::valueOf(value.downstreamThroughputKbps.value()) : nullptr, + value.upstreamThroughputKbps.has_value() ? jni::JDouble::valueOf(value.upstreamThroughputKbps.value()) : nullptr, + value.httpRttMs.has_value() ? jni::JDouble::valueOf(value.httpRttMs.value()) : nullptr, + value.transportRttMs.has_value() ? jni::JDouble::valueOf(value.transportRttMs.value()) : nullptr, + value.effectiveConnectionType.has_value() ? jni::make_jstring(value.effectiveConnectionType.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::nitrofetch diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt index c6a8cee..7cb4083 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt @@ -47,6 +47,10 @@ abstract class HybridNitroFetchClientSpec: HybridObject() { @DoNotStrip @Keep abstract fun prefetch(req: NitroRequest): Promise + + @DoNotStrip + @Keep + abstract fun getNetworkQualityEstimate(): NetworkQualityEstimate private external fun initHybrid(): HybridData diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NetworkQualityEstimate.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NetworkQualityEstimate.kt new file mode 100644 index 0000000..6687802 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NetworkQualityEstimate.kt @@ -0,0 +1,41 @@ +/// +/// NetworkQualityEstimate.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrofetch + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + + +/** + * Represents the JavaScript object/struct "NetworkQualityEstimate". + */ +@DoNotStrip +@Keep +data class NetworkQualityEstimate + @DoNotStrip + @Keep + constructor( + @DoNotStrip + @Keep + val downstreamThroughputKbps: Double?, + @DoNotStrip + @Keep + val upstreamThroughputKbps: Double?, + @DoNotStrip + @Keep + val httpRttMs: Double?, + @DoNotStrip + @Keep + val transportRttMs: Double?, + @DoNotStrip + @Keep + val effectiveConnectionType: String? + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/nitrofetch+autolinking.cmake b/nitrogen/generated/android/nitrofetch+autolinking.cmake index 1512532..e44f071 100644 --- a/nitrogen/generated/android/nitrofetch+autolinking.cmake +++ b/nitrogen/generated/android/nitrofetch+autolinking.cmake @@ -13,12 +13,6 @@ # include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/nitrofetch+autolinking.cmake) # ``` -# Define a flag to check if we are building properly -add_definitions(-DBUILDING_NITROFETCH_WITH_GENERATED_CMAKE_PROJECT) - -# Enable Raw Props parsing in react-native (for Nitro Views) -add_definitions(-DRN_SERIALIZABLE_STATE) - # Add all headers that were generated by Nitrogen include_directories( "../nitrogen/generated/shared/c++" @@ -40,9 +34,12 @@ target_sources( ../nitrogen/generated/android/c++/JHybridNitroFetchSpec.cpp ) +# Define a flag to check if we are building properly +add_definitions(-DBUILDING_NITROFETCH_WITH_GENERATED_CMAKE_PROJECT) + # From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake # Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake -target_compile_definitions( + target_compile_definitions( nitrofetch PRIVATE -DFOLLY_NO_CONFIG=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 diff --git a/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp b/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp index cc4a79d..2434016 100644 --- a/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp +++ b/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp @@ -12,6 +12,8 @@ namespace margelo::nitro::nitrofetch { class HybridNitroFetchClientSpec; } // Forward declaration of `HybridNitroFetchSpec` to properly resolve imports. namespace margelo::nitro::nitrofetch { class HybridNitroFetchSpec; } +// Forward declaration of `NetworkQualityEstimate` to properly resolve imports. +namespace margelo::nitro::nitrofetch { struct NetworkQualityEstimate; } // Forward declaration of `NitroHeader` to properly resolve imports. namespace margelo::nitro::nitrofetch { struct NitroHeader; } // Forward declaration of `NitroRequestMethod` to properly resolve imports. @@ -28,6 +30,7 @@ namespace NitroFetch { class HybridNitroFetchSpec_cxx; } // Include C++ defined types #include "HybridNitroFetchClientSpec.hpp" #include "HybridNitroFetchSpec.hpp" +#include "NetworkQualityEstimate.hpp" #include "NitroHeader.hpp" #include "NitroRequestMethod.hpp" #include "NitroResponse.hpp" @@ -253,6 +256,15 @@ namespace margelo::nitro::nitrofetch::bridge::swift { return Result>>::withError(error); } + // pragma MARK: Result + using Result_NetworkQualityEstimate_ = Result; + inline Result_NetworkQualityEstimate_ create_Result_NetworkQualityEstimate_(const NetworkQualityEstimate& value) noexcept { + return Result::withValue(value); + } + inline Result_NetworkQualityEstimate_ create_Result_NetworkQualityEstimate_(const std::exception_ptr& error) noexcept { + return Result::withError(error); + } + // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. diff --git a/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp b/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp index cccdc32..2cb3a02 100644 --- a/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp +++ b/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp @@ -12,6 +12,8 @@ namespace margelo::nitro::nitrofetch { class HybridNitroFetchClientSpec; } // Forward declaration of `HybridNitroFetchSpec` to properly resolve imports. namespace margelo::nitro::nitrofetch { class HybridNitroFetchSpec; } +// Forward declaration of `NetworkQualityEstimate` to properly resolve imports. +namespace margelo::nitro::nitrofetch { struct NetworkQualityEstimate; } // Forward declaration of `NitroHeader` to properly resolve imports. namespace margelo::nitro::nitrofetch { struct NitroHeader; } // Forward declaration of `NitroRequestMethod` to properly resolve imports. @@ -24,6 +26,7 @@ namespace margelo::nitro::nitrofetch { struct NitroResponse; } // Include C++ defined types #include "HybridNitroFetchClientSpec.hpp" #include "HybridNitroFetchSpec.hpp" +#include "NetworkQualityEstimate.hpp" #include "NitroHeader.hpp" #include "NitroRequest.hpp" #include "NitroRequestMethod.hpp" diff --git a/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp b/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp index 7e0882b..5bf989b 100644 --- a/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp +++ b/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp @@ -20,6 +20,8 @@ namespace margelo::nitro::nitrofetch { struct NitroHeader; } namespace margelo::nitro::nitrofetch { struct NitroRequest; } // Forward declaration of `NitroRequestMethod` to properly resolve imports. namespace margelo::nitro::nitrofetch { enum class NitroRequestMethod; } +// Forward declaration of `NetworkQualityEstimate` to properly resolve imports. +namespace margelo::nitro::nitrofetch { struct NetworkQualityEstimate; } #include "NitroResponse.hpp" #include @@ -29,6 +31,7 @@ namespace margelo::nitro::nitrofetch { enum class NitroRequestMethod; } #include #include "NitroRequest.hpp" #include "NitroRequestMethod.hpp" +#include "NetworkQualityEstimate.hpp" #include "NitroFetch-Swift-Cxx-Umbrella.hpp" @@ -87,6 +90,14 @@ namespace margelo::nitro::nitrofetch { auto __value = std::move(__result.value()); return __value; } + inline NetworkQualityEstimate getNetworkQualityEstimate() override { + auto __result = _swiftPart.getNetworkQualityEstimate(); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } private: NitroFetch::HybridNitroFetchClientSpec_cxx _swiftPart; diff --git a/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift b/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift index 1548ec5..aae5ea4 100644 --- a/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift +++ b/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift @@ -16,6 +16,7 @@ public protocol HybridNitroFetchClientSpec_protocol: HybridObject { // Methods func request(req: NitroRequest) throws -> Promise func prefetch(req: NitroRequest) throws -> Promise + func getNetworkQualityEstimate() throws -> NetworkQualityEstimate } /// See ``HybridNitroFetchClientSpec`` diff --git a/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift b/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift index 2cc0866..ffc6798 100644 --- a/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift @@ -146,4 +146,16 @@ open class HybridNitroFetchClientSpec_cxx { return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr) } } + + @inline(__always) + public final func getNetworkQualityEstimate() -> bridge.Result_NetworkQualityEstimate_ { + do { + let __result = try self.__implementation.getNetworkQualityEstimate() + let __resultCpp = __result + return bridge.create_Result_NetworkQualityEstimate_(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_NetworkQualityEstimate_(__exceptionPtr) + } + } } diff --git a/nitrogen/generated/ios/swift/NetworkQualityEstimate.swift b/nitrogen/generated/ios/swift/NetworkQualityEstimate.swift new file mode 100644 index 0000000..1ebb1dd --- /dev/null +++ b/nitrogen/generated/ios/swift/NetworkQualityEstimate.swift @@ -0,0 +1,146 @@ +/// +/// NetworkQualityEstimate.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `NetworkQualityEstimate`, backed by a C++ struct. + */ +public typealias NetworkQualityEstimate = margelo.nitro.nitrofetch.NetworkQualityEstimate + +public extension NetworkQualityEstimate { + private typealias bridge = margelo.nitro.nitrofetch.bridge.swift + + /** + * Create a new instance of `NetworkQualityEstimate`. + */ + init(downstreamThroughputKbps: Double?, upstreamThroughputKbps: Double?, httpRttMs: Double?, transportRttMs: Double?, effectiveConnectionType: String?) { + self.init({ () -> bridge.std__optional_double_ in + if let __unwrappedValue = downstreamThroughputKbps { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = upstreamThroughputKbps { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = httpRttMs { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = transportRttMs { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = effectiveConnectionType { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }()) + } + + var downstreamThroughputKbps: Double? { + @inline(__always) + get { + return self.__downstreamThroughputKbps.value + } + @inline(__always) + set { + self.__downstreamThroughputKbps = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var upstreamThroughputKbps: Double? { + @inline(__always) + get { + return self.__upstreamThroughputKbps.value + } + @inline(__always) + set { + self.__upstreamThroughputKbps = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var httpRttMs: Double? { + @inline(__always) + get { + return self.__httpRttMs.value + } + @inline(__always) + set { + self.__httpRttMs = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var transportRttMs: Double? { + @inline(__always) + get { + return self.__transportRttMs.value + } + @inline(__always) + set { + self.__transportRttMs = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var effectiveConnectionType: String? { + @inline(__always) + get { + return { () -> String? in + if bridge.has_value_std__optional_std__string_(self.__effectiveConnectionType) { + let __unwrapped = bridge.get_std__optional_std__string_(self.__effectiveConnectionType) + return String(__unwrapped) + } else { + return nil + } + }() + } + @inline(__always) + set { + self.__effectiveConnectionType = { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }() + } + } +} diff --git a/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp b/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp index 1540766..08c1509 100644 --- a/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp +++ b/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp @@ -16,6 +16,7 @@ namespace margelo::nitro::nitrofetch { registerHybrids(this, [](Prototype& prototype) { prototype.registerHybridMethod("request", &HybridNitroFetchClientSpec::request); prototype.registerHybridMethod("prefetch", &HybridNitroFetchClientSpec::prefetch); + prototype.registerHybridMethod("getNetworkQualityEstimate", &HybridNitroFetchClientSpec::getNetworkQualityEstimate); }); } diff --git a/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp b/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp index 936fff5..0f73050 100644 --- a/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp +++ b/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp @@ -17,10 +17,13 @@ namespace margelo::nitro::nitrofetch { struct NitroResponse; } // Forward declaration of `NitroRequest` to properly resolve imports. namespace margelo::nitro::nitrofetch { struct NitroRequest; } +// Forward declaration of `NetworkQualityEstimate` to properly resolve imports. +namespace margelo::nitro::nitrofetch { struct NetworkQualityEstimate; } #include "NitroResponse.hpp" #include #include "NitroRequest.hpp" +#include "NetworkQualityEstimate.hpp" namespace margelo::nitro::nitrofetch { @@ -55,6 +58,7 @@ namespace margelo::nitro::nitrofetch { // Methods virtual std::shared_ptr> request(const NitroRequest& req) = 0; virtual std::shared_ptr> prefetch(const NitroRequest& req) = 0; + virtual NetworkQualityEstimate getNetworkQualityEstimate() = 0; protected: // Hybrid Setup diff --git a/nitrogen/generated/shared/c++/NetworkQualityEstimate.hpp b/nitrogen/generated/shared/c++/NetworkQualityEstimate.hpp new file mode 100644 index 0000000..cd1fd8b --- /dev/null +++ b/nitrogen/generated/shared/c++/NetworkQualityEstimate.hpp @@ -0,0 +1,84 @@ +/// +/// NetworkQualityEstimate.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + + + +#include +#include + +namespace margelo::nitro::nitrofetch { + + /** + * A struct which can be represented as a JavaScript object (NetworkQualityEstimate). + */ + struct NetworkQualityEstimate { + public: + std::optional downstreamThroughputKbps SWIFT_PRIVATE; + std::optional upstreamThroughputKbps SWIFT_PRIVATE; + std::optional httpRttMs SWIFT_PRIVATE; + std::optional transportRttMs SWIFT_PRIVATE; + std::optional effectiveConnectionType SWIFT_PRIVATE; + + public: + NetworkQualityEstimate() = default; + explicit NetworkQualityEstimate(std::optional downstreamThroughputKbps, std::optional upstreamThroughputKbps, std::optional httpRttMs, std::optional transportRttMs, std::optional effectiveConnectionType): downstreamThroughputKbps(downstreamThroughputKbps), upstreamThroughputKbps(upstreamThroughputKbps), httpRttMs(httpRttMs), transportRttMs(transportRttMs), effectiveConnectionType(effectiveConnectionType) {} + }; + +} // namespace margelo::nitro::nitrofetch + +namespace margelo::nitro { + + // C++ NetworkQualityEstimate <> JS NetworkQualityEstimate (object) + template <> + struct JSIConverter final { + static inline margelo::nitro::nitrofetch::NetworkQualityEstimate fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return margelo::nitro::nitrofetch::NetworkQualityEstimate( + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "downstreamThroughputKbps")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "upstreamThroughputKbps")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "httpRttMs")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "transportRttMs")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "effectiveConnectionType")) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitrofetch::NetworkQualityEstimate& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, "downstreamThroughputKbps", JSIConverter>::toJSI(runtime, arg.downstreamThroughputKbps)); + obj.setProperty(runtime, "upstreamThroughputKbps", JSIConverter>::toJSI(runtime, arg.upstreamThroughputKbps)); + obj.setProperty(runtime, "httpRttMs", JSIConverter>::toJSI(runtime, arg.httpRttMs)); + obj.setProperty(runtime, "transportRttMs", JSIConverter>::toJSI(runtime, arg.transportRttMs)); + obj.setProperty(runtime, "effectiveConnectionType", JSIConverter>::toJSI(runtime, arg.effectiveConnectionType)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "downstreamThroughputKbps"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "upstreamThroughputKbps"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "httpRttMs"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "transportRttMs"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "effectiveConnectionType"))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/src/NitroFetch.nitro.ts b/src/NitroFetch.nitro.ts index b44cbff..f0a91e5 100644 --- a/src/NitroFetch.nitro.ts +++ b/src/NitroFetch.nitro.ts @@ -39,12 +39,22 @@ export interface NitroResponse { bodyBytes?: string; //will be ArrayBuffer in future } +export interface NetworkQualityEstimate { + downstreamThroughputKbps?: number; + upstreamThroughputKbps?: number; + httpRttMs?: number; + transportRttMs?: number; + effectiveConnectionType?: string; +} + export interface NitroFetchClient extends HybridObject<{ ios: 'swift'; android: 'kotlin'; }> { // Client-binded request that uses the env configured at creation. request(req: NitroRequest): Promise; // Start a prefetch for a given request; expects a header `prefetchKey`. prefetch(req: NitroRequest): Promise; + // Get network quality estimate from Cronet (Android only, uses Network Quality Estimator) + getNetworkQualityEstimate(): NetworkQualityEstimate; } export interface NitroFetch diff --git a/src/fetch.ts b/src/fetch.ts index a9e67ec..d09ca0d 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -294,6 +294,22 @@ export async function removeAllFromAutoprefetch(): Promise { } } +// Get network quality estimate from Cronet's Network Quality Estimator +export function getNetworkQualityEstimate() { + const hasNative = typeof (NitroFetchHybrid as any)?.createClient === 'function'; + if (!hasNative) { + throw new Error('NitroFetch native module not available'); + } + + // Create a fresh client to get the latest network quality estimate + const freshClient = NitroFetchHybrid.createClient(); + if (!freshClient || typeof (freshClient as any).getNetworkQualityEstimate !== 'function') { + throw new Error('getNetworkQualityEstimate not supported'); + } + + return freshClient.getNetworkQualityEstimate(); +} + // Optional off-thread processing using react-native-worklets-core export type NitroWorkletMapper = (payload: { url: string; diff --git a/src/index.tsx b/src/index.tsx index 6b911d2..47fed65 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,8 +5,10 @@ export { prefetchOnAppStart, removeFromAutoPrefetch, removeAllFromAutoprefetch, + getNetworkQualityEstimate, } from './fetch'; export type { NitroRequest, NitroResponse } from './fetch'; +export type { NetworkQualityEstimate } from './NitroFetch.nitro'; export { NitroFetch } from './NitroInstances'; import './fetch';