react-native-nitro-fetch 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +3 -1
  2. package/android/src/main/java/com/margelo/nitro/nitrofetch/CronetExtensions.kt +31 -0
  3. package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroCronet.kt +89 -0
  4. package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +115 -28
  5. package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroUrlRequest.kt +35 -0
  6. package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroUrlRequestBuilder.kt +181 -0
  7. package/ios/HybridNitroCronet.swift +31 -0
  8. package/ios/HybridUrlRequest.swift +41 -0
  9. package/ios/HybridUrlRequestBuilder.swift +255 -0
  10. package/ios/NitroAutoPrefetcher.swift +3 -1
  11. package/ios/NitroFetchClient.swift +87 -5
  12. package/ios/URLSessionExtensions.swift +36 -0
  13. package/lib/module/NitroCronet.nitro.js +4 -0
  14. package/lib/module/NitroCronet.nitro.js.map +1 -0
  15. package/lib/module/NitroInstances.js +1 -0
  16. package/lib/module/NitroInstances.js.map +1 -1
  17. package/lib/module/fetch.js +179 -9
  18. package/lib/module/fetch.js.map +1 -1
  19. package/lib/typescript/src/NitroCronet.nitro.d.ts +53 -0
  20. package/lib/typescript/src/NitroCronet.nitro.d.ts.map +1 -0
  21. package/lib/typescript/src/NitroFetch.nitro.d.ts +10 -0
  22. package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -1
  23. package/lib/typescript/src/NitroInstances.d.ts +3 -1
  24. package/lib/typescript/src/NitroInstances.d.ts.map +1 -1
  25. package/lib/typescript/src/fetch.d.ts +5 -2
  26. package/lib/typescript/src/fetch.d.ts.map +1 -1
  27. package/lib/typescript/src/index.d.ts +1 -1
  28. package/lib/typescript/src/index.d.ts.map +1 -1
  29. package/nitro.json +34 -6
  30. package/nitrogen/generated/android/c++/JFunc_void_UrlResponseInfo.hpp +82 -0
  31. package/nitrogen/generated/android/c++/JFunc_void_UrlResponseInfo_std__shared_ptr_ArrayBuffer__double.hpp +84 -0
  32. package/nitrogen/generated/android/c++/JFunc_void_UrlResponseInfo_std__string.hpp +82 -0
  33. package/nitrogen/generated/android/c++/JFunc_void_std__optional_UrlResponseInfo_.hpp +83 -0
  34. package/nitrogen/generated/android/c++/JFunc_void_std__optional_UrlResponseInfo__RequestException.hpp +85 -0
  35. package/nitrogen/generated/android/c++/JHttpHeader.hpp +61 -0
  36. package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp +23 -16
  37. package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp +20 -21
  38. package/nitrogen/generated/android/c++/JHybridNitroCronetSpec.cpp +57 -0
  39. package/nitrogen/generated/android/c++/JHybridNitroCronetSpec.hpp +63 -0
  40. package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp +32 -16
  41. package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp +21 -21
  42. package/nitrogen/generated/android/c++/JHybridNitroFetchSpec.cpp +22 -15
  43. package/nitrogen/generated/android/c++/JHybridNitroFetchSpec.hpp +20 -21
  44. package/nitrogen/generated/android/c++/JHybridUrlRequestBuilderSpec.cpp +123 -0
  45. package/nitrogen/generated/android/c++/JHybridUrlRequestBuilderSpec.hpp +73 -0
  46. package/nitrogen/generated/android/c++/JHybridUrlRequestSpec.cpp +69 -0
  47. package/nitrogen/generated/android/c++/JHybridUrlRequestSpec.hpp +67 -0
  48. package/nitrogen/generated/android/c++/JNitroFormDataPart.hpp +74 -0
  49. package/nitrogen/generated/android/c++/JNitroHeader.hpp +7 -3
  50. package/nitrogen/generated/android/c++/JNitroRequest.hpp +39 -6
  51. package/nitrogen/generated/android/c++/JNitroRequestMethod.hpp +9 -10
  52. package/nitrogen/generated/android/c++/JNitroResponse.hpp +9 -4
  53. package/nitrogen/generated/android/c++/JRequestException.hpp +57 -0
  54. package/nitrogen/generated/android/c++/JUrlResponseInfo.hpp +146 -0
  55. package/nitrogen/generated/android/c++/JVariant_ArrayBuffer_String.cpp +26 -0
  56. package/nitrogen/generated/android/c++/JVariant_ArrayBuffer_String.hpp +70 -0
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_UrlResponseInfo.kt +80 -0
  58. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_UrlResponseInfo_std__shared_ptr_ArrayBuffer__double.kt +80 -0
  59. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_UrlResponseInfo_std__string.kt +80 -0
  60. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_std__optional_UrlResponseInfo_.kt +80 -0
  61. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_std__optional_UrlResponseInfo__RequestException.kt +80 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HttpHeader.kt +41 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt +18 -16
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroCronetSpec.kt +54 -0
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt +23 -16
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchSpec.kt +18 -16
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridUrlRequestBuilderSpec.kt +125 -0
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridUrlRequestSpec.kt +70 -0
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroFormDataPart.kt +50 -0
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroHeader.kt +19 -10
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequest.kt +40 -25
  72. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequestMethod.kt +3 -1
  73. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroResponse.kt +39 -30
  74. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/RequestException.kt +38 -0
  75. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/UrlResponseInfo.kt +65 -0
  76. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Variant_ArrayBuffer_String.kt +53 -0
  77. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/nitrofetchOnLoad.kt +1 -1
  78. package/nitrogen/generated/android/nitrofetch+autolinking.cmake +8 -1
  79. package/nitrogen/generated/android/nitrofetch+autolinking.gradle +1 -1
  80. package/nitrogen/generated/android/nitrofetchOnLoad.cpp +85 -33
  81. package/nitrogen/generated/android/nitrofetchOnLoad.hpp +14 -5
  82. package/nitrogen/generated/ios/NitroFetch+autolinking.rb +2 -2
  83. package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp +102 -10
  84. package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp +359 -24
  85. package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp +32 -1
  86. package/nitrogen/generated/ios/NitroFetchAutolinking.mm +9 -1
  87. package/nitrogen/generated/ios/NitroFetchAutolinking.swift +29 -22
  88. package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp +1 -1
  89. package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp +10 -1
  90. package/nitrogen/generated/ios/c++/HybridNitroCronetSpecSwift.cpp +11 -0
  91. package/nitrogen/generated/ios/c++/HybridNitroCronetSpecSwift.hpp +85 -0
  92. package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.cpp +1 -1
  93. package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp +22 -4
  94. package/nitrogen/generated/ios/c++/HybridNitroFetchSpecSwift.cpp +1 -1
  95. package/nitrogen/generated/ios/c++/HybridNitroFetchSpecSwift.hpp +10 -1
  96. package/nitrogen/generated/ios/c++/HybridUrlRequestBuilderSpecSwift.cpp +11 -0
  97. package/nitrogen/generated/ios/c++/HybridUrlRequestBuilderSpecSwift.hpp +163 -0
  98. package/nitrogen/generated/ios/c++/HybridUrlRequestSpecSwift.cpp +11 -0
  99. package/nitrogen/generated/ios/c++/HybridUrlRequestSpecSwift.hpp +106 -0
  100. package/nitrogen/generated/ios/swift/Func_void.swift +1 -2
  101. package/nitrogen/generated/ios/swift/Func_void_NitroResponse.swift +1 -2
  102. package/nitrogen/generated/ios/swift/Func_void_UrlResponseInfo.swift +46 -0
  103. package/nitrogen/generated/ios/swift/Func_void_UrlResponseInfo_std__shared_ptr_ArrayBuffer__double.swift +46 -0
  104. package/nitrogen/generated/ios/swift/Func_void_UrlResponseInfo_std__string.swift +46 -0
  105. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +1 -2
  106. package/nitrogen/generated/ios/swift/Func_void_std__optional_UrlResponseInfo_.swift +46 -0
  107. package/nitrogen/generated/ios/swift/Func_void_std__optional_UrlResponseInfo__RequestException.swift +46 -0
  108. package/nitrogen/generated/ios/swift/HttpHeader.swift +34 -0
  109. package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift +10 -4
  110. package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift +18 -3
  111. package/nitrogen/generated/ios/swift/HybridNitroCronetSpec.swift +55 -0
  112. package/nitrogen/generated/ios/swift/HybridNitroCronetSpec_cxx.swift +141 -0
  113. package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift +11 -4
  114. package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift +29 -3
  115. package/nitrogen/generated/ios/swift/HybridNitroFetchSpec.swift +10 -4
  116. package/nitrogen/generated/ios/swift/HybridNitroFetchSpec_cxx.swift +18 -3
  117. package/nitrogen/generated/ios/swift/HybridUrlRequestBuilderSpec.swift +65 -0
  118. package/nitrogen/generated/ios/swift/HybridUrlRequestBuilderSpec_cxx.swift +305 -0
  119. package/nitrogen/generated/ios/swift/HybridUrlRequestSpec.swift +59 -0
  120. package/nitrogen/generated/ios/swift/HybridUrlRequestSpec_cxx.swift +182 -0
  121. package/nitrogen/generated/ios/swift/NitroFormDataPart.swift +101 -0
  122. package/nitrogen/generated/ios/swift/NitroHeader.swift +5 -17
  123. package/nitrogen/generated/ios/swift/NitroRequest.swift +111 -121
  124. package/nitrogen/generated/ios/swift/NitroRequestMethod.swift +1 -1
  125. package/nitrogen/generated/ios/swift/NitroResponse.swift +40 -97
  126. package/nitrogen/generated/ios/swift/RequestException.swift +29 -0
  127. package/nitrogen/generated/ios/swift/UrlResponseInfo.swift +118 -0
  128. package/nitrogen/generated/ios/swift/Variant_ArrayBuffer_String.swift +18 -0
  129. package/nitrogen/generated/shared/c++/HttpHeader.hpp +87 -0
  130. package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp +1 -1
  131. package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp +1 -1
  132. package/nitrogen/generated/shared/c++/HybridNitroCronetSpec.cpp +21 -0
  133. package/nitrogen/generated/shared/c++/HybridNitroCronetSpec.hpp +65 -0
  134. package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp +2 -1
  135. package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp +3 -1
  136. package/nitrogen/generated/shared/c++/HybridNitroFetchSpec.cpp +1 -1
  137. package/nitrogen/generated/shared/c++/HybridNitroFetchSpec.hpp +1 -1
  138. package/nitrogen/generated/shared/c++/HybridUrlRequestBuilderSpec.cpp +31 -0
  139. package/nitrogen/generated/shared/c++/HybridUrlRequestBuilderSpec.hpp +85 -0
  140. package/nitrogen/generated/shared/c++/HybridUrlRequestSpec.cpp +25 -0
  141. package/nitrogen/generated/shared/c++/HybridUrlRequestSpec.hpp +66 -0
  142. package/nitrogen/generated/shared/c++/NitroFormDataPart.hpp +100 -0
  143. package/nitrogen/generated/shared/c++/NitroHeader.hpp +24 -8
  144. package/nitrogen/generated/shared/c++/NitroRequest.hpp +51 -24
  145. package/nitrogen/generated/shared/c++/NitroRequestMethod.hpp +1 -1
  146. package/nitrogen/generated/shared/c++/NitroResponse.hpp +42 -26
  147. package/nitrogen/generated/shared/c++/RequestException.hpp +83 -0
  148. package/nitrogen/generated/shared/c++/UrlResponseInfo.hpp +123 -0
  149. package/package.json +13 -10
  150. package/src/NitroCronet.nitro.ts +72 -0
  151. package/src/NitroFetch.nitro.ts +28 -6
  152. package/src/NitroInstances.ts +4 -0
  153. package/src/fetch.ts +214 -13
  154. package/src/index.tsx +1 -1
@@ -39,8 +39,10 @@ object AutoPrefetcher {
39
39
  headers = headerObjs,
40
40
  bodyString = null,
41
41
  bodyBytes = null,
42
+ bodyFormData = null,
42
43
  timeoutMs = null,
43
- followRedirects = null
44
+ followRedirects = null,
45
+ requestId = null
44
46
  )
45
47
 
46
48
  // If already pending or fresh, skip starting a new one
@@ -0,0 +1,31 @@
1
+ package com.margelo.nitro.nitrofetch
2
+
3
+ import org.chromium.net.UrlResponseInfo as CronetUrlResponseInfo
4
+ import org.chromium.net.CronetException as CronetNativeException
5
+
6
+ fun CronetUrlResponseInfo.toNitro(): UrlResponseInfo {
7
+ val headersMap = mutableMapOf<String, String>()
8
+ val headersList = mutableListOf<HttpHeader>()
9
+
10
+ allHeadersAsList.forEach { header ->
11
+ headersMap[header.key] = header.value
12
+ headersList.add(HttpHeader(key = header.key, value = header.value))
13
+ }
14
+
15
+ return UrlResponseInfo(
16
+ url = url,
17
+ httpStatusCode = httpStatusCode.toDouble(),
18
+ httpStatusText = httpStatusText ?: "",
19
+ allHeaders = headersMap,
20
+ allHeadersAsList = headersList.toTypedArray(),
21
+ urlChain = urlChain.toTypedArray(),
22
+ negotiatedProtocol = negotiatedProtocol,
23
+ proxyServer = proxyServer,
24
+ receivedByteCount = receivedByteCount.toDouble(),
25
+ wasCached = wasCached()
26
+ )
27
+ }
28
+
29
+ fun CronetNativeException.toNitro(): RequestException {
30
+ return RequestException(message = message ?: "Unknown Cronet error")
31
+ }
@@ -0,0 +1,89 @@
1
+ package com.margelo.nitro.nitrofetch
2
+
3
+ import android.app.Application
4
+ import com.facebook.proguard.annotations.DoNotStrip
5
+ import org.chromium.net.CronetEngine
6
+ import org.chromium.net.CronetProvider
7
+ import java.io.File
8
+ import java.util.concurrent.Executor
9
+ import java.util.concurrent.Executors
10
+
11
+ /**
12
+ * Streaming-focused Nitro Cronet client.
13
+ * Manages the Cronet engine and provides URL request building for streaming.
14
+ */
15
+ @DoNotStrip
16
+ class NitroCronet : HybridNitroCronetSpec() {
17
+
18
+ override fun newUrlRequestBuilder(url: String): HybridUrlRequestBuilderSpec {
19
+ return NitroUrlRequestBuilder(
20
+ engine = getOrCreateCronetEngine(),
21
+ url = url,
22
+ executor = ioExecutor
23
+ )
24
+ }
25
+
26
+ companion object {
27
+ @Volatile
28
+ private var engineRef: CronetEngine? = null
29
+
30
+ val ioExecutor: Executor by lazy {
31
+ val cores = Runtime.getRuntime().availableProcessors().coerceAtLeast(2)
32
+ Executors.newFixedThreadPool(cores) { r ->
33
+ Thread(r, "NitroCronet-io").apply {
34
+ isDaemon = true
35
+ priority = Thread.NORM_PRIORITY
36
+ }
37
+ }
38
+ }
39
+
40
+ fun getOrCreateCronetEngine(): CronetEngine {
41
+ engineRef?.let { return it }
42
+ synchronized(this) {
43
+ engineRef?.let { return it }
44
+
45
+ val app = currentApplication() ?: initialApplication()
46
+ ?: throw IllegalStateException("NitroCronet: Application not available")
47
+
48
+ val providers = CronetProvider.getAllProviders(app)
49
+
50
+ val nativeProvider = providers.firstOrNull {
51
+ it.name.contains("Native", ignoreCase = true)
52
+ }
53
+
54
+ val cacheDir = File(app.cacheDir, "nitro_cronet_cache").apply { mkdirs() }
55
+ val builder = (nativeProvider?.createBuilder() ?: CronetEngine.Builder(app))
56
+ .enableHttp2(true)
57
+ .enableQuic(true)
58
+ .enableBrotli(true)
59
+ .setStoragePath(cacheDir.absolutePath)
60
+ .setUserAgent("NitroCronet/1.0")
61
+
62
+ builder.enableHttpCache(
63
+ CronetEngine.Builder.HTTP_CACHE_DISK,
64
+ (50 * 1024 * 1024).toLong()
65
+ )
66
+
67
+ val engine = builder.build()
68
+ engineRef = engine
69
+ return engine
70
+ }
71
+ }
72
+
73
+ private fun currentApplication(): Application? = try {
74
+ val cls = Class.forName("android.app.ActivityThread")
75
+ val m = cls.getMethod("currentApplication")
76
+ m.invoke(null) as? Application
77
+ } catch (_: Throwable) {
78
+ null
79
+ }
80
+
81
+ private fun initialApplication(): Application? = try {
82
+ val cls = Class.forName("android.app.AppGlobals")
83
+ val m = cls.getMethod("getInitialApplication")
84
+ m.invoke(null) as? Application
85
+ } catch (_: Throwable) {
86
+ null
87
+ }
88
+ }
89
+ }
@@ -1,14 +1,20 @@
1
1
  package com.margelo.nitro.nitrofetch
2
2
 
3
+ import android.net.Uri
3
4
  import android.util.Log
4
5
  import com.facebook.proguard.annotations.DoNotStrip
6
+ import com.margelo.nitro.NitroModules
5
7
  import com.margelo.nitro.core.ArrayBuffer
6
8
  import com.margelo.nitro.core.Promise
7
9
  import org.chromium.net.CronetEngine
8
10
  import org.chromium.net.CronetException
9
11
  import org.chromium.net.UrlRequest
10
12
  import org.chromium.net.UrlResponseInfo
13
+ import java.io.ByteArrayOutputStream
14
+ import java.io.File
11
15
  import java.nio.ByteBuffer
16
+ import java.util.UUID
17
+ import java.util.concurrent.ConcurrentHashMap
12
18
  import java.util.concurrent.Executor
13
19
 
14
20
  fun ByteBuffer.toByteArray(): ByteArray {
@@ -22,6 +28,13 @@ fun ByteBuffer.toByteArray(): ByteArray {
22
28
 
23
29
  @DoNotStrip
24
30
  class NitroFetchClient(private val engine: CronetEngine, private val executor: Executor) : HybridNitroFetchClientSpec() {
31
+
32
+ private val activeRequests = ConcurrentHashMap<String, UrlRequest>()
33
+
34
+ override fun cancelRequest(requestId: String) {
35
+ // https://developer.android.com/develop/connectivity/cronet/reference/org/chromium/net/UrlRequest#cancel()
36
+ activeRequests.remove(requestId)?.cancel()
37
+ }
25
38
 
26
39
  private fun findPrefetchKey(req: NitroRequest): String? {
27
40
  val h = req.headers ?: return null
@@ -39,13 +52,14 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E
39
52
  req: NitroRequest,
40
53
  onSuccess: (NitroResponse) -> Unit,
41
54
  onFail: (Throwable) -> Unit
42
- ) {
43
- try {
55
+ ): UrlRequest? {
56
+ return try {
44
57
  val engine = NitroFetch.getEngine()
45
58
  val executor = NitroFetch.ioExecutor
46
59
  startCronet(engine, executor, req, onSuccess, onFail)
47
60
  } catch (t: Throwable) {
48
61
  onFail(t)
62
+ null
49
63
  }
50
64
  }
51
65
 
@@ -55,7 +69,7 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E
55
69
  req: NitroRequest,
56
70
  onSuccess: (NitroResponse) -> Unit,
57
71
  onFail: (Throwable) -> Unit
58
- ) {
72
+ ): UrlRequest {
59
73
  val url = req.url
60
74
  val callback = object : UrlRequest.Callback() {
61
75
  private val buffer = ByteBuffer.allocateDirect(16 * 1024)
@@ -125,33 +139,94 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E
125
139
  val method = req.method?.name ?: "GET"
126
140
  builder.setHttpMethod(method)
127
141
  req.headers?.forEach { (k, v) -> builder.addHeader(k, v) }
128
- val bodyBytes = req.bodyBytes
129
- val bodyStr = req.bodyString
130
- if ((bodyBytes != null) || !bodyStr.isNullOrEmpty()) {
131
- val body: ByteArray = when {
132
- bodyBytes != null -> ByteArray(1);//bodyBytes.getBuffer(true).toByteArray()
133
- !bodyStr.isNullOrEmpty() -> bodyStr!!.toByteArray(Charsets.UTF_8)
134
- else -> ByteArray(0)
135
- }
136
- val provider = object : org.chromium.net.UploadDataProvider() {
137
- private var pos = 0
138
- override fun getLength(): Long = body.size.toLong()
139
- override fun read(uploadDataSink: org.chromium.net.UploadDataSink, byteBuffer: ByteBuffer) {
140
- val remaining = body.size - pos
141
- val toWrite = minOf(byteBuffer.remaining(), remaining)
142
- byteBuffer.put(body, pos, toWrite)
143
- pos += toWrite
144
- uploadDataSink.onReadSucceeded(false)
145
- }
146
- override fun rewind(uploadDataSink: org.chromium.net.UploadDataSink) {
147
- pos = 0
148
- uploadDataSink.onRewindSucceeded()
142
+
143
+ val formParts = req.bodyFormData
144
+ if (formParts != null && formParts.isNotEmpty()) {
145
+ val (multipartBody, contentType) = buildMultipartBody(formParts)
146
+ builder.addHeader("Content-Type", contentType)
147
+ val provider = createUploadProvider(multipartBody)
148
+ builder.setUploadDataProvider(provider, executor)
149
+ } else {
150
+ val bodyBytes = req.bodyBytes
151
+ val bodyStr = req.bodyString
152
+ if ((bodyBytes != null) || !bodyStr.isNullOrEmpty()) {
153
+ val body: ByteArray = when {
154
+ bodyBytes != null -> ByteArray(1)
155
+ !bodyStr.isNullOrEmpty() -> bodyStr!!.toByteArray(Charsets.UTF_8)
156
+ else -> ByteArray(0)
149
157
  }
158
+ val provider = createUploadProvider(body)
159
+ builder.setUploadDataProvider(provider, executor)
150
160
  }
151
- builder.setUploadDataProvider(provider, executor)
152
161
  }
162
+
153
163
  val request = builder.build()
154
164
  request.start()
165
+ return request
166
+ }
167
+
168
+ private fun createUploadProvider(body: ByteArray): org.chromium.net.UploadDataProvider {
169
+ return object : org.chromium.net.UploadDataProvider() {
170
+ private var pos = 0
171
+ override fun getLength(): Long = body.size.toLong()
172
+ override fun read(uploadDataSink: org.chromium.net.UploadDataSink, byteBuffer: ByteBuffer) {
173
+ val remaining = body.size - pos
174
+ val toWrite = minOf(byteBuffer.remaining(), remaining)
175
+ byteBuffer.put(body, pos, toWrite)
176
+ pos += toWrite
177
+ uploadDataSink.onReadSucceeded(false)
178
+ }
179
+ override fun rewind(uploadDataSink: org.chromium.net.UploadDataSink) {
180
+ pos = 0
181
+ uploadDataSink.onRewindSucceeded()
182
+ }
183
+ }
184
+ }
185
+
186
+ private fun buildMultipartBody(parts: Array<NitroFormDataPart>): Pair<ByteArray, String> {
187
+ val boundary = "NitroFetch-${UUID.randomUUID()}"
188
+ val out = ByteArrayOutputStream()
189
+ val crlf = "\r\n".toByteArray()
190
+
191
+ for (part in parts) {
192
+ out.write("--$boundary\r\n".toByteArray())
193
+
194
+ val fileUri = part.fileUri
195
+ if (fileUri != null) {
196
+ val fileName = part.fileName ?: "file"
197
+ val mimeType = part.mimeType ?: "application/octet-stream"
198
+ out.write("Content-Disposition: form-data; name=\"${part.name}\"; filename=\"$fileName\"\r\n".toByteArray())
199
+ out.write("Content-Type: $mimeType\r\n\r\n".toByteArray())
200
+
201
+ val fileData = readFileBytes(fileUri)
202
+ out.write(fileData)
203
+ } else {
204
+ val value = part.value ?: ""
205
+ out.write("Content-Disposition: form-data; name=\"${part.name}\"\r\n\r\n".toByteArray())
206
+ out.write(value.toByteArray(Charsets.UTF_8))
207
+ }
208
+
209
+ out.write(crlf)
210
+ }
211
+
212
+ out.write("--$boundary--\r\n".toByteArray())
213
+ return Pair(out.toByteArray(), "multipart/form-data; boundary=$boundary")
214
+ }
215
+
216
+ private fun readFileBytes(uri: String): ByteArray {
217
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
218
+ val url = java.net.URL(uri)
219
+ return url.openStream().use { it.readBytes() }
220
+ }
221
+ if (uri.startsWith("content://")) {
222
+ val context = NitroModules.applicationContext
223
+ ?: throw IllegalStateException("Cannot read content:// URI - no Android Context")
224
+ val inputStream = context.contentResolver.openInputStream(Uri.parse(uri))
225
+ ?: throw IllegalArgumentException("Cannot open content URI: $uri")
226
+ return inputStream.use { it.readBytes() }
227
+ }
228
+ val path = if (uri.startsWith("file://")) uri.removePrefix("file://") else uri
229
+ return File(path).readBytes()
155
230
  }
156
231
  }
157
232
 
@@ -229,11 +304,23 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E
229
304
  return promise
230
305
  }
231
306
  }
232
- fetch(
307
+ val requestId = req.requestId
308
+ val urlRequest = fetch(
233
309
  req,
234
- onSuccess = { promise.resolve(it) },
235
- onFail = { promise.reject(it) }
310
+ onSuccess = { res ->
311
+ if (requestId != null) activeRequests.remove(requestId)
312
+ promise.resolve(res)
313
+ },
314
+ onFail = { err ->
315
+ if (requestId != null) activeRequests.remove(requestId)
316
+ promise.reject(err)
317
+ }
236
318
  )
319
+ // Store after start() — if cancelRequest races and misses, the JS
320
+ // catch block checks signal.aborted and throws AbortError anyway.
321
+ if (requestId != null && urlRequest != null) {
322
+ activeRequests[requestId] = urlRequest
323
+ }
237
324
  return promise
238
325
  }
239
326
 
@@ -0,0 +1,35 @@
1
+ package com.margelo.nitro.nitrofetch
2
+
3
+ import com.facebook.proguard.annotations.DoNotStrip
4
+ import org.chromium.net.UrlRequest as CronetUrlRequest
5
+ import java.nio.ByteBuffer
6
+
7
+ @DoNotStrip
8
+ class NitroUrlRequest(
9
+ private val cronetRequest: CronetUrlRequest,
10
+ private val byteBuffer: ByteBuffer
11
+ ) : HybridUrlRequestSpec() {
12
+
13
+ override fun start() {
14
+ cronetRequest.start()
15
+ }
16
+
17
+ override fun followRedirect() {
18
+ cronetRequest.followRedirect()
19
+ }
20
+
21
+ override fun read() {
22
+ // Prepare buffer for writing (position=0, limit=capacity)
23
+ byteBuffer.clear()
24
+ // Pass to Cronet to fill
25
+ cronetRequest.read(byteBuffer)
26
+ }
27
+
28
+ override fun cancel() {
29
+ cronetRequest.cancel()
30
+ }
31
+
32
+ override fun isDone(): Boolean {
33
+ return cronetRequest.isDone
34
+ }
35
+ }
@@ -0,0 +1,181 @@
1
+ package com.margelo.nitro.nitrofetch
2
+
3
+ import com.facebook.proguard.annotations.DoNotStrip
4
+ import com.margelo.nitro.core.ArrayBuffer
5
+ import org.chromium.net.CronetEngine
6
+ import org.chromium.net.UrlRequest as CronetUrlRequest
7
+ import org.chromium.net.UrlResponseInfo as CronetUrlResponseInfo
8
+ import org.chromium.net.CronetException as CronetNativeException
9
+ import java.nio.ByteBuffer
10
+ import java.util.concurrent.Executor as JavaExecutor
11
+
12
+ @DoNotStrip
13
+ class NitroUrlRequestBuilder(
14
+ private val engine: CronetEngine,
15
+ private val url: String,
16
+ private val executor: JavaExecutor
17
+ ) : HybridUrlRequestBuilderSpec() {
18
+
19
+ // Callbacks stored as optionals and set via setter methods
20
+ private var onRedirectReceivedCallback: ((info: UrlResponseInfo, newLocationUrl: String) -> Unit)? = null
21
+ private var onResponseStartedCallback: ((info: UrlResponseInfo) -> Unit)? = null
22
+ private var onReadCompletedCallback: ((info: UrlResponseInfo, byteBuffer: ArrayBuffer, bytesRead: Double) -> Unit)? = null
23
+ private var onSucceededCallback: ((info: UrlResponseInfo) -> Unit)? = null
24
+ private var onFailedCallback: ((info: UrlResponseInfo?, error: RequestException) -> Unit)? = null
25
+ private var onCanceledCallback: ((info: UrlResponseInfo?) -> Unit)? = null
26
+
27
+ private val builder: CronetUrlRequest.Builder
28
+ private val byteBuffer: ByteBuffer
29
+
30
+ init {
31
+ // Allocate ONE reusable owning buffer for all reads (64KB)
32
+ val reusableBuffer = ArrayBuffer.allocate(65536)
33
+ byteBuffer = reusableBuffer.getBuffer(false)
34
+
35
+ val cronetCallback = object : CronetUrlRequest.Callback() {
36
+
37
+ override fun onRedirectReceived(
38
+ request: CronetUrlRequest,
39
+ info: CronetUrlResponseInfo,
40
+ newLocationUrl: String
41
+ ) {
42
+ onRedirectReceivedCallback?.let { callback ->
43
+ val nitroInfo = info.toNitro()
44
+ callback(nitroInfo, newLocationUrl)
45
+ }
46
+ }
47
+
48
+ override fun onResponseStarted(
49
+ request: CronetUrlRequest,
50
+ info: CronetUrlResponseInfo
51
+ ) {
52
+ onResponseStartedCallback?.let { callback ->
53
+ val nitroInfo = info.toNitro()
54
+ callback(nitroInfo)
55
+ }
56
+ }
57
+
58
+ override fun onReadCompleted(
59
+ request: CronetUrlRequest,
60
+ info: CronetUrlResponseInfo,
61
+ receivedBuffer: ByteBuffer
62
+ ) {
63
+ onReadCompletedCallback?.let { callback ->
64
+ val bytesRead = receivedBuffer.position()
65
+ val nitroInfo = info.toNitro()
66
+ callback(nitroInfo, reusableBuffer, bytesRead.toDouble())
67
+ }
68
+ }
69
+
70
+ override fun onSucceeded(
71
+ request: CronetUrlRequest,
72
+ info: CronetUrlResponseInfo
73
+ ) {
74
+ onSucceededCallback?.let { callback ->
75
+ val nitroInfo = info.toNitro()
76
+ callback(nitroInfo)
77
+ }
78
+ }
79
+
80
+ override fun onFailed(
81
+ request: CronetUrlRequest,
82
+ info: CronetUrlResponseInfo?,
83
+ error: CronetNativeException
84
+ ) {
85
+ onFailedCallback?.let { callback ->
86
+ val nitroInfo = info?.toNitro()
87
+ val nitroError = error.toNitro()
88
+ callback(nitroInfo, nitroError)
89
+ }
90
+ }
91
+
92
+ override fun onCanceled(
93
+ request: CronetUrlRequest,
94
+ info: CronetUrlResponseInfo?
95
+ ) {
96
+ onCanceledCallback?.let { callback ->
97
+ val nitroInfo = info?.toNitro()
98
+ callback(nitroInfo)
99
+ }
100
+ }
101
+ }
102
+
103
+ builder = engine.newUrlRequestBuilder(url, cronetCallback, executor)
104
+ }
105
+
106
+ override fun setHttpMethod(httpMethod: String) {
107
+ builder.setHttpMethod(httpMethod)
108
+ }
109
+
110
+ override fun addHeader(name: String, value: String) {
111
+ builder.addHeader(name, value)
112
+ }
113
+
114
+ override fun setUploadBody(body: Variant_ArrayBuffer_String) {
115
+ val bodyBytes: ByteArray = when (body) {
116
+ is Variant_ArrayBuffer_String.First -> {
117
+ val buffer = body.value.getBuffer(true)
118
+ val bytes = ByteArray(buffer.remaining())
119
+ buffer.get(bytes)
120
+ bytes
121
+ }
122
+ is Variant_ArrayBuffer_String.Second -> body.value.toByteArray(Charsets.UTF_8)
123
+ }
124
+
125
+ val provider = object : org.chromium.net.UploadDataProvider() {
126
+ private var position = 0
127
+
128
+ override fun getLength(): Long = bodyBytes.size.toLong()
129
+
130
+ override fun read(uploadDataSink: org.chromium.net.UploadDataSink, byteBuffer: ByteBuffer) {
131
+ val remaining = bodyBytes.size - position
132
+ val toWrite = minOf(byteBuffer.remaining(), remaining)
133
+ if (toWrite > 0) {
134
+ byteBuffer.put(bodyBytes, position, toWrite)
135
+ position += toWrite
136
+ }
137
+ uploadDataSink.onReadSucceeded(false)
138
+ }
139
+
140
+ override fun rewind(uploadDataSink: org.chromium.net.UploadDataSink) {
141
+ position = 0
142
+ uploadDataSink.onRewindSucceeded()
143
+ }
144
+ }
145
+
146
+ builder.setUploadDataProvider(provider, executor)
147
+ }
148
+
149
+ override fun disableCache() {
150
+ builder.disableCache()
151
+ }
152
+
153
+ override fun onSucceeded(callback: (info: UrlResponseInfo) -> Unit) {
154
+ this.onSucceededCallback = callback
155
+ }
156
+
157
+ override fun onFailed(callback: (info: UrlResponseInfo?, error: RequestException) -> Unit) {
158
+ this.onFailedCallback = callback
159
+ }
160
+
161
+ override fun onCanceled(callback: (info: UrlResponseInfo?) -> Unit) {
162
+ this.onCanceledCallback = callback
163
+ }
164
+
165
+ override fun onRedirectReceived(callback: (info: UrlResponseInfo, newLocationUrl: String) -> Unit) {
166
+ this.onRedirectReceivedCallback = callback
167
+ }
168
+
169
+ override fun onResponseStarted(callback: (info: UrlResponseInfo) -> Unit) {
170
+ this.onResponseStartedCallback = callback
171
+ }
172
+
173
+ override fun onReadCompleted(callback: (info: UrlResponseInfo, byteBuffer: ArrayBuffer, bytesRead: Double) -> Unit) {
174
+ this.onReadCompletedCallback = callback
175
+ }
176
+
177
+ override fun build(): HybridUrlRequestSpec {
178
+ val cronetRequest = builder.build()
179
+ return NitroUrlRequest(cronetRequest, byteBuffer)
180
+ }
181
+ }
@@ -0,0 +1,31 @@
1
+ import Foundation
2
+ import NitroModules
3
+
4
+ class HybridNitroCronet: HybridNitroCronetSpec {
5
+ // Shared URLSession for all streaming requests
6
+ private static let session: URLSession = {
7
+ let config = URLSessionConfiguration.default
8
+ config.requestCachePolicy = .useProtocolCachePolicy
9
+ config.urlCache = URLCache(
10
+ memoryCapacity: 32 * 1024 * 1024,
11
+ diskCapacity: 100 * 1024 * 1024,
12
+ diskPath: "nitrofetch_urlcache"
13
+ )
14
+ return URLSession(configuration: config)
15
+ }()
16
+
17
+ // Shared executor queue
18
+ private static let executorQueue = DispatchQueue(
19
+ label: "com.nitrofetch.executor",
20
+ qos: .userInitiated,
21
+ attributes: .concurrent
22
+ )
23
+
24
+ func newUrlRequestBuilder(url: String) throws -> any HybridUrlRequestBuilderSpec {
25
+ return HybridUrlRequestBuilder(
26
+ url: url,
27
+ session: HybridNitroCronet.session,
28
+ executor: HybridNitroCronet.executorQueue
29
+ )
30
+ }
31
+ }
@@ -0,0 +1,41 @@
1
+ import Foundation
2
+ import NitroModules
3
+
4
+ class HybridUrlRequest: HybridUrlRequestSpec {
5
+ private weak var task: URLSessionDataTask?
6
+ private weak var delegate: URLSessionDelegate?
7
+ private var isDoneFlag = false
8
+
9
+ init(task: URLSessionDataTask, delegate: URLSessionDelegate) {
10
+ self.task = task
11
+ self.delegate = delegate
12
+ super.init()
13
+ }
14
+
15
+ func start() throws {
16
+ task?.resume()
17
+ }
18
+
19
+ func followRedirect() throws {
20
+ // Redirects are handled automatically by URLSession
21
+ // This is a no-op on iOS but required for API compatibility
22
+ }
23
+
24
+ func read() throws {
25
+ // Reading is handled automatically by URLSession delegate
26
+ // This is a no-op on iOS but required for API compatibility
27
+ }
28
+
29
+ func cancel() throws {
30
+ task?.cancel()
31
+ isDoneFlag = true
32
+ }
33
+
34
+ func isDone() throws -> Bool {
35
+ return isDoneFlag
36
+ }
37
+
38
+ func markDone() {
39
+ isDoneFlag = true
40
+ }
41
+ }