react-native-nitro-fetch 0.1.6 → 0.2.0
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.
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/nitrofetch/CronetExtensions.kt +31 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroCronet.kt +89 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +115 -28
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroUrlRequest.kt +35 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroUrlRequestBuilder.kt +181 -0
- package/ios/HybridNitroCronet.swift +31 -0
- package/ios/HybridUrlRequest.swift +41 -0
- package/ios/HybridUrlRequestBuilder.swift +255 -0
- package/ios/NitroAutoPrefetcher.swift +3 -1
- package/ios/NitroFetchClient.swift +87 -5
- package/ios/URLSessionExtensions.swift +36 -0
- package/lib/module/NitroCronet.nitro.js +4 -0
- package/lib/module/NitroCronet.nitro.js.map +1 -0
- package/lib/module/NitroInstances.js +1 -0
- package/lib/module/NitroInstances.js.map +1 -1
- package/lib/module/fetch.js +179 -9
- package/lib/module/fetch.js.map +1 -1
- package/lib/typescript/src/NitroCronet.nitro.d.ts +53 -0
- package/lib/typescript/src/NitroCronet.nitro.d.ts.map +1 -0
- package/lib/typescript/src/NitroFetch.nitro.d.ts +10 -0
- package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -1
- package/lib/typescript/src/NitroInstances.d.ts +3 -1
- package/lib/typescript/src/NitroInstances.d.ts.map +1 -1
- package/lib/typescript/src/fetch.d.ts +5 -2
- package/lib/typescript/src/fetch.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/nitro.json +34 -6
- package/nitrogen/generated/android/c++/JFunc_void_UrlResponseInfo.hpp +82 -0
- package/nitrogen/generated/android/c++/JFunc_void_UrlResponseInfo_std__shared_ptr_ArrayBuffer__double.hpp +84 -0
- package/nitrogen/generated/android/c++/JFunc_void_UrlResponseInfo_std__string.hpp +82 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_UrlResponseInfo_.hpp +83 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_UrlResponseInfo__RequestException.hpp +85 -0
- package/nitrogen/generated/android/c++/JHttpHeader.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp +23 -16
- package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp +20 -21
- package/nitrogen/generated/android/c++/JHybridNitroCronetSpec.cpp +57 -0
- package/nitrogen/generated/android/c++/JHybridNitroCronetSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp +32 -16
- package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp +21 -21
- package/nitrogen/generated/android/c++/JHybridNitroFetchSpec.cpp +22 -15
- package/nitrogen/generated/android/c++/JHybridNitroFetchSpec.hpp +20 -21
- package/nitrogen/generated/android/c++/JHybridUrlRequestBuilderSpec.cpp +123 -0
- package/nitrogen/generated/android/c++/JHybridUrlRequestBuilderSpec.hpp +73 -0
- package/nitrogen/generated/android/c++/JHybridUrlRequestSpec.cpp +69 -0
- package/nitrogen/generated/android/c++/JHybridUrlRequestSpec.hpp +67 -0
- package/nitrogen/generated/android/c++/JNitroFormDataPart.hpp +74 -0
- package/nitrogen/generated/android/c++/JNitroHeader.hpp +7 -3
- package/nitrogen/generated/android/c++/JNitroRequest.hpp +39 -6
- package/nitrogen/generated/android/c++/JNitroRequestMethod.hpp +9 -10
- package/nitrogen/generated/android/c++/JNitroResponse.hpp +9 -4
- package/nitrogen/generated/android/c++/JRequestException.hpp +57 -0
- package/nitrogen/generated/android/c++/JUrlResponseInfo.hpp +146 -0
- package/nitrogen/generated/android/c++/JVariant_ArrayBuffer_String.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_ArrayBuffer_String.hpp +70 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_UrlResponseInfo.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_UrlResponseInfo_std__shared_ptr_ArrayBuffer__double.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_UrlResponseInfo_std__string.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_std__optional_UrlResponseInfo_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Func_void_std__optional_UrlResponseInfo__RequestException.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HttpHeader.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt +18 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroCronetSpec.kt +54 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt +23 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchSpec.kt +18 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridUrlRequestBuilderSpec.kt +125 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridUrlRequestSpec.kt +70 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroFormDataPart.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroHeader.kt +19 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequest.kt +40 -25
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequestMethod.kt +3 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroResponse.kt +39 -30
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/RequestException.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/UrlResponseInfo.kt +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/Variant_ArrayBuffer_String.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/nitrofetchOnLoad.kt +1 -1
- package/nitrogen/generated/android/nitrofetch+autolinking.cmake +8 -1
- package/nitrogen/generated/android/nitrofetch+autolinking.gradle +1 -1
- package/nitrogen/generated/android/nitrofetchOnLoad.cpp +85 -33
- package/nitrogen/generated/android/nitrofetchOnLoad.hpp +14 -5
- package/nitrogen/generated/ios/NitroFetch+autolinking.rb +2 -2
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp +102 -10
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp +359 -24
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp +32 -1
- package/nitrogen/generated/ios/NitroFetchAutolinking.mm +9 -1
- package/nitrogen/generated/ios/NitroFetchAutolinking.swift +29 -22
- package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp +1 -1
- package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp +10 -1
- package/nitrogen/generated/ios/c++/HybridNitroCronetSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNitroCronetSpecSwift.hpp +85 -0
- package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.cpp +1 -1
- package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp +22 -4
- package/nitrogen/generated/ios/c++/HybridNitroFetchSpecSwift.cpp +1 -1
- package/nitrogen/generated/ios/c++/HybridNitroFetchSpecSwift.hpp +10 -1
- package/nitrogen/generated/ios/c++/HybridUrlRequestBuilderSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridUrlRequestBuilderSpecSwift.hpp +163 -0
- package/nitrogen/generated/ios/c++/HybridUrlRequestSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridUrlRequestSpecSwift.hpp +106 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +1 -2
- package/nitrogen/generated/ios/swift/Func_void_NitroResponse.swift +1 -2
- package/nitrogen/generated/ios/swift/Func_void_UrlResponseInfo.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_UrlResponseInfo_std__shared_ptr_ArrayBuffer__double.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_UrlResponseInfo_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +1 -2
- package/nitrogen/generated/ios/swift/Func_void_std__optional_UrlResponseInfo_.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_UrlResponseInfo__RequestException.swift +46 -0
- package/nitrogen/generated/ios/swift/HttpHeader.swift +34 -0
- package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift +10 -4
- package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift +18 -3
- package/nitrogen/generated/ios/swift/HybridNitroCronetSpec.swift +55 -0
- package/nitrogen/generated/ios/swift/HybridNitroCronetSpec_cxx.swift +141 -0
- package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift +11 -4
- package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift +29 -3
- package/nitrogen/generated/ios/swift/HybridNitroFetchSpec.swift +10 -4
- package/nitrogen/generated/ios/swift/HybridNitroFetchSpec_cxx.swift +18 -3
- package/nitrogen/generated/ios/swift/HybridUrlRequestBuilderSpec.swift +65 -0
- package/nitrogen/generated/ios/swift/HybridUrlRequestBuilderSpec_cxx.swift +305 -0
- package/nitrogen/generated/ios/swift/HybridUrlRequestSpec.swift +59 -0
- package/nitrogen/generated/ios/swift/HybridUrlRequestSpec_cxx.swift +182 -0
- package/nitrogen/generated/ios/swift/NitroFormDataPart.swift +101 -0
- package/nitrogen/generated/ios/swift/NitroHeader.swift +5 -17
- package/nitrogen/generated/ios/swift/NitroRequest.swift +111 -121
- package/nitrogen/generated/ios/swift/NitroRequestMethod.swift +1 -1
- package/nitrogen/generated/ios/swift/NitroResponse.swift +40 -97
- package/nitrogen/generated/ios/swift/RequestException.swift +29 -0
- package/nitrogen/generated/ios/swift/UrlResponseInfo.swift +118 -0
- package/nitrogen/generated/ios/swift/Variant_ArrayBuffer_String.swift +18 -0
- package/nitrogen/generated/shared/c++/HttpHeader.hpp +87 -0
- package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNitroCronetSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridNitroCronetSpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp +2 -1
- package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp +3 -1
- package/nitrogen/generated/shared/c++/HybridNitroFetchSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNitroFetchSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/HybridUrlRequestBuilderSpec.cpp +31 -0
- package/nitrogen/generated/shared/c++/HybridUrlRequestBuilderSpec.hpp +85 -0
- package/nitrogen/generated/shared/c++/HybridUrlRequestSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridUrlRequestSpec.hpp +66 -0
- package/nitrogen/generated/shared/c++/NitroFormDataPart.hpp +100 -0
- package/nitrogen/generated/shared/c++/NitroHeader.hpp +24 -8
- package/nitrogen/generated/shared/c++/NitroRequest.hpp +51 -24
- package/nitrogen/generated/shared/c++/NitroRequestMethod.hpp +1 -1
- package/nitrogen/generated/shared/c++/NitroResponse.hpp +42 -26
- package/nitrogen/generated/shared/c++/RequestException.hpp +83 -0
- package/nitrogen/generated/shared/c++/UrlResponseInfo.hpp +123 -0
- package/package.json +10 -10
- package/src/NitroCronet.nitro.ts +72 -0
- package/src/NitroFetch.nitro.ts +28 -6
- package/src/NitroInstances.ts +4 -0
- package/src/fetch.ts +214 -13
- package/src/index.tsx +1 -1
package/android/build.gradle
CHANGED
|
@@ -110,7 +110,7 @@ repositories {
|
|
|
110
110
|
|
|
111
111
|
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
112
112
|
// ---------- Cronet (Java API only) ----------
|
|
113
|
-
def cronetVersion = (getExtOrDefault("cronetVersion") ?: "
|
|
113
|
+
def cronetVersion = (getExtOrDefault("cronetVersion") ?: "141.7340.3")
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
dependencies {
|
|
@@ -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
|
-
|
|
129
|
-
val
|
|
130
|
-
if (
|
|
131
|
-
val
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
val
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
307
|
+
val requestId = req.requestId
|
|
308
|
+
val urlRequest = fetch(
|
|
233
309
|
req,
|
|
234
|
-
onSuccess = {
|
|
235
|
-
|
|
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
|
+
}
|