react-native-worklets 0.7.2 → 0.8.0-bundle-mode-preview-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.
- package/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp +32 -28
- package/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h +13 -5
- package/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.cpp +7 -5
- package/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.h +5 -4
- package/Common/cpp/worklets/Resources/SynchronizableUnpacker.cpp +5 -5
- package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.cpp +42 -19
- package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.h +2 -0
- package/Common/cpp/worklets/Tools/Defs.h +2 -2
- package/Common/cpp/worklets/Tools/ScriptBuffer.h +34 -0
- package/Common/cpp/worklets/WorkletRuntime/RuntimeBindings.h +24 -0
- package/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp +11 -6
- package/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.cpp +82 -0
- package/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.h +12 -0
- package/RNWorklets.podspec +15 -14
- package/android/CMakeLists.txt +8 -2
- package/android/build.gradle +92 -56
- package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.cpp +67 -0
- package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.h +48 -0
- package/android/src/main/cpp/worklets/android/JWorkletRuntimeWrapper.cpp +52 -0
- package/android/src/main/cpp/worklets/android/JWorkletRuntimeWrapper.h +43 -0
- package/android/src/main/cpp/worklets/android/WorkletsModule.cpp +115 -19
- package/android/src/main/cpp/worklets/android/WorkletsModule.h +11 -13
- package/android/src/main/cpp/worklets/android/WorkletsOnLoad.cpp +6 -0
- package/android/src/main/java/com/swmansion/worklets/ScriptBufferWrapper.java +88 -0
- package/android/src/networking/com/swmansion/worklets/WorkletRuntimeWrapper.kt +23 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsHeaderUtil.kt +30 -0
- package/android/src/{legacyBundling → networking}/com/swmansion/worklets/WorkletsModule.java +52 -2
- package/android/src/networking/com/swmansion/worklets/WorkletsNetworkEventUtil.kt +268 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsNetworking.kt +1084 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsOkHttpCallUtil.kt +37 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsProgressListener.kt +9 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsProgressRequestBody.kt +98 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsProgressResponseBody.kt +57 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsProgressiveStringDecoder.kt +82 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsRequestBodyUtil.kt +177 -0
- package/android/src/{experimentalBundling → no-networking}/com/swmansion/worklets/WorkletsModule.java +10 -15
- package/apple/worklets/apple/Networking/WorkletsNetworking.h +22 -0
- package/apple/worklets/apple/Networking/WorkletsNetworking.mm +706 -0
- package/apple/worklets/apple/WorkletsModule.mm +56 -17
- package/bundleMode/index.js +2 -6
- package/compatibility.json +4 -1
- package/lib/module/WorkletsModule/NativeWorklets.native.js +8 -2
- package/lib/module/WorkletsModule/NativeWorklets.native.js.map +1 -1
- package/lib/module/bundleMode/metroOverrides.native.js +115 -0
- package/lib/module/bundleMode/metroOverrides.native.js.map +1 -0
- package/lib/module/bundleMode/network.native.js +41 -0
- package/lib/module/bundleMode/network.native.js.map +1 -0
- package/lib/module/debug/jsVersion.js +1 -1
- package/lib/module/debug/jsVersion.js.map +1 -1
- package/lib/module/featureFlags/staticFlags.json +2 -0
- package/lib/module/featureFlags/types.js +3 -1
- package/lib/module/featureFlags/types.js.map +1 -1
- package/lib/module/index.js +4 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/initializers/initializers.native.js +24 -50
- package/lib/module/initializers/initializers.native.js.map +1 -1
- package/lib/module/initializers/workletRuntimeEntry.native.js +3 -3
- package/lib/module/initializers/workletRuntimeEntry.native.js.map +1 -1
- package/lib/module/memory/bundleUnpacker.native.js +2 -2
- package/lib/module/memory/bundleUnpacker.native.js.map +1 -1
- package/lib/module/memory/serializable.native.js +3 -3
- package/lib/module/memory/serializable.native.js.map +1 -1
- package/lib/module/memory/synchronizableUnpacker.native.js +3 -3
- package/lib/module/memory/synchronizableUnpacker.native.js.map +1 -1
- package/lib/module/platformChecker.js +2 -2
- package/lib/module/platformChecker.js.map +1 -1
- package/lib/module/runtimeKind.js +51 -0
- package/lib/module/runtimeKind.js.map +1 -1
- package/lib/module/runtimes.js +3 -0
- package/lib/module/runtimes.js.map +1 -1
- package/lib/module/runtimes.native.js +34 -3
- package/lib/module/runtimes.native.js.map +1 -1
- package/lib/module/threads.native.js +2 -2
- package/lib/module/threads.native.js.map +1 -1
- package/lib/typescript/WorkletsModule/NativeWorklets.native.d.ts.map +1 -1
- package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts +2 -1
- package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts.map +1 -1
- package/lib/typescript/bundleMode/metroOverrides.native.d.ts +28 -0
- package/lib/typescript/bundleMode/metroOverrides.native.d.ts.map +1 -0
- package/lib/typescript/bundleMode/network.native.d.ts +7 -0
- package/lib/typescript/bundleMode/network.native.d.ts.map +1 -0
- package/lib/typescript/debug/jsVersion.d.ts +1 -1
- package/lib/typescript/debug/jsVersion.d.ts.map +1 -1
- package/lib/typescript/featureFlags/types.d.ts +3 -1
- package/lib/typescript/featureFlags/types.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/initializers/initializers.native.d.ts +1 -0
- package/lib/typescript/initializers/initializers.native.d.ts.map +1 -1
- package/lib/typescript/initializers/workletRuntimeEntry.native.d.ts +1 -1
- package/lib/typescript/memory/bundleUnpacker.native.d.ts.map +1 -1
- package/lib/typescript/memory/synchronizableUnpacker.native.d.ts.map +1 -1
- package/lib/typescript/platformChecker.d.ts.map +1 -1
- package/lib/typescript/runtimeKind.d.ts +31 -0
- package/lib/typescript/runtimeKind.d.ts.map +1 -1
- package/lib/typescript/runtimes.d.ts +1 -0
- package/lib/typescript/runtimes.d.ts.map +1 -1
- package/lib/typescript/runtimes.native.d.ts +20 -2
- package/lib/typescript/runtimes.native.d.ts.map +1 -1
- package/lib/typescript/threads.native.d.ts +1 -1
- package/package.json +8 -6
- package/plugin/index.d.ts +109 -0
- package/plugin/index.js +59 -9
- package/scripts/worklets_utils.rb +21 -5
- package/src/WorkletsModule/NativeWorklets.native.ts +14 -4
- package/src/WorkletsModule/workletsModuleProxy.ts +6 -3
- package/src/bundleMode/metroOverrides.native.ts +151 -0
- package/src/bundleMode/network.native.ts +59 -0
- package/src/debug/jsVersion.ts +1 -1
- package/src/featureFlags/staticFlags.json +2 -0
- package/src/featureFlags/types.ts +3 -1
- package/src/index.ts +10 -1
- package/src/initializers/initializers.native.ts +29 -70
- package/src/initializers/workletRuntimeEntry.native.ts +3 -3
- package/src/memory/bundleUnpacker.native.ts +2 -4
- package/src/memory/serializable.native.ts +3 -3
- package/src/memory/synchronizableUnpacker.native.ts +6 -12
- package/src/platformChecker.ts +3 -2
- package/src/privateGlobals.d.ts +7 -2
- package/src/runtimeKind.ts +47 -0
- package/src/runtimes.native.ts +43 -2
- package/src/runtimes.ts +10 -0
- package/src/threads.native.ts +2 -2
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on Networking.kt from React Native
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Conflicting okhttp versions
|
|
6
|
+
@file:Suppress("DEPRECATION_ERROR")
|
|
7
|
+
|
|
8
|
+
package com.swmansion.worklets
|
|
9
|
+
|
|
10
|
+
import android.net.Uri
|
|
11
|
+
import android.util.Base64
|
|
12
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
|
+
import com.facebook.react.bridge.ReadableArray
|
|
14
|
+
import com.facebook.react.bridge.ReadableMap
|
|
15
|
+
import com.facebook.react.bridge.ReadableType
|
|
16
|
+
import com.facebook.react.bridge.WritableMap
|
|
17
|
+
import com.facebook.react.modules.network.CookieJarContainer
|
|
18
|
+
import com.facebook.react.modules.network.ForwardingCookieHandler
|
|
19
|
+
import com.facebook.react.modules.network.NetworkInterceptorCreator;
|
|
20
|
+
import com.facebook.react.modules.network.NetworkingModule
|
|
21
|
+
import com.facebook.react.modules.network.OkHttpClientProvider
|
|
22
|
+
import java.io.IOException
|
|
23
|
+
import java.nio.charset.StandardCharsets
|
|
24
|
+
import java.util.ArrayList
|
|
25
|
+
import java.util.HashSet
|
|
26
|
+
import java.util.UUID
|
|
27
|
+
import java.util.concurrent.TimeUnit
|
|
28
|
+
import okhttp3.Call
|
|
29
|
+
import okhttp3.Callback
|
|
30
|
+
import okhttp3.CookieJar
|
|
31
|
+
import okhttp3.Headers
|
|
32
|
+
import okhttp3.JavaNetCookieJar
|
|
33
|
+
import okhttp3.MediaType
|
|
34
|
+
import okhttp3.MultipartBody
|
|
35
|
+
import okhttp3.OkHttpClient
|
|
36
|
+
import okhttp3.Protocol
|
|
37
|
+
import okhttp3.Request
|
|
38
|
+
import okhttp3.RequestBody
|
|
39
|
+
import okhttp3.Response
|
|
40
|
+
import okhttp3.ResponseBody
|
|
41
|
+
import okio.ByteString
|
|
42
|
+
import okio.GzipSource
|
|
43
|
+
import okio.Okio
|
|
44
|
+
|
|
45
|
+
private class RequestId(
|
|
46
|
+
runtimeId: Int,
|
|
47
|
+
requestId: Int
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
/** Implements the XMLHttpRequest JavaScript interface. */
|
|
51
|
+
public class WorkletsNetworking(
|
|
52
|
+
defaultUserAgent: String?,
|
|
53
|
+
client: OkHttpClient,
|
|
54
|
+
networkInterceptorCreators: List<NetworkInterceptorCreator>?,
|
|
55
|
+
) {
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Allows to implement a custom fetching process for specific URIs. It is the handler's job to
|
|
59
|
+
* fetch the URI and return the JS body payload.
|
|
60
|
+
*/
|
|
61
|
+
internal interface UriHandler {
|
|
62
|
+
/** Returns if the handler should be used for an URI. */
|
|
63
|
+
public fun supports(uri: Uri, responseType: String): Boolean
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Fetch the URI and return a tuple containing the JS body payload and the raw response body.
|
|
67
|
+
*/
|
|
68
|
+
@Throws(IOException::class) public fun fetch(uri: Uri): Pair<WritableMap, ByteArray>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Allows adding custom handling to build the [RequestBody] from the JS body payload. */
|
|
72
|
+
internal interface RequestBodyHandler {
|
|
73
|
+
/** Returns if the handler should be used for a JS body payload. */
|
|
74
|
+
public fun supports(map: ReadableMap): Boolean
|
|
75
|
+
|
|
76
|
+
/** Returns the [RequestBody] for the JS body payload. */
|
|
77
|
+
public fun toRequestBody(map: ReadableMap, contentType: String?): RequestBody?
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Allows adding custom handling to build the JS body payload from the [ResponseBody]. */
|
|
81
|
+
internal interface ResponseHandler {
|
|
82
|
+
/** Returns if the handler should be used for a response type. */
|
|
83
|
+
public fun supports(responseType: String): Boolean
|
|
84
|
+
|
|
85
|
+
/** Returns the JS body payload for the [ResponseBody]. */
|
|
86
|
+
@Throws(IOException::class) public fun toResponseData(data: ByteArray): WritableMap
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private val client: OkHttpClient
|
|
90
|
+
private val cookieHandler = ForwardingCookieHandler()
|
|
91
|
+
private val defaultUserAgent: String?
|
|
92
|
+
private var cookieJarContainer: CookieJarContainer? = null
|
|
93
|
+
// private val requestIds: MutableSet<Int> = HashSet()
|
|
94
|
+
private val requestIds: MutableMap<Int, MutableSet<Int>> = HashMap()
|
|
95
|
+
private val requestBodyHandlers: MutableList<RequestBodyHandler> = ArrayList()
|
|
96
|
+
private val uriHandlers: MutableList<UriHandler> = ArrayList()
|
|
97
|
+
private val responseHandlers: MutableList<ResponseHandler> = ArrayList()
|
|
98
|
+
private var shuttingDown = false
|
|
99
|
+
|
|
100
|
+
init {
|
|
101
|
+
var resolvedClient: OkHttpClient = client
|
|
102
|
+
if (networkInterceptorCreators != null) {
|
|
103
|
+
val clientBuilder = client.newBuilder()
|
|
104
|
+
for (networkInterceptorCreator in networkInterceptorCreators) {
|
|
105
|
+
clientBuilder.addNetworkInterceptor(networkInterceptorCreator.create())
|
|
106
|
+
}
|
|
107
|
+
resolvedClient = clientBuilder.build()
|
|
108
|
+
}
|
|
109
|
+
this.client = resolvedClient
|
|
110
|
+
|
|
111
|
+
val cookieJar = resolvedClient.cookieJar()
|
|
112
|
+
cookieJarContainer =
|
|
113
|
+
if (cookieJar is CookieJarContainer) {
|
|
114
|
+
cookieJar
|
|
115
|
+
} else {
|
|
116
|
+
null
|
|
117
|
+
}
|
|
118
|
+
this.defaultUserAgent = defaultUserAgent
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param context the ReactContext of the application
|
|
123
|
+
* @param defaultUserAgent the User-Agent header that will be set for all requests where the
|
|
124
|
+
* caller does not provide one explicitly
|
|
125
|
+
* @param client the [OkHttpClient] to be used for networking
|
|
126
|
+
*/
|
|
127
|
+
internal constructor(
|
|
128
|
+
defaultUserAgent: String?,
|
|
129
|
+
client: OkHttpClient,
|
|
130
|
+
) : this(defaultUserAgent, client, null)
|
|
131
|
+
|
|
132
|
+
/** @param context the ReactContext of the application */
|
|
133
|
+
public constructor(
|
|
134
|
+
) : this(null, OkHttpClientProvider.createClient(
|
|
135
|
+
), null)
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param context the ReactContext of the application
|
|
139
|
+
* @param networkInterceptorCreators list of [NetworkInterceptorCreator]'s whose create() methods
|
|
140
|
+
* would be called to attach the interceptors to the client.
|
|
141
|
+
*/
|
|
142
|
+
public constructor(
|
|
143
|
+
networkInterceptorCreators: List<NetworkInterceptorCreator>?,
|
|
144
|
+
) : this(
|
|
145
|
+
null,
|
|
146
|
+
OkHttpClientProvider.createClient(
|
|
147
|
+
),
|
|
148
|
+
networkInterceptorCreators,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param context the ReactContext of the application
|
|
153
|
+
* @param defaultUserAgent the User-Agent header that will be set for all requests where the
|
|
154
|
+
* caller does not provide one explicitly
|
|
155
|
+
*/
|
|
156
|
+
public constructor(
|
|
157
|
+
defaultUserAgent: String?,
|
|
158
|
+
) : this(
|
|
159
|
+
defaultUserAgent,
|
|
160
|
+
OkHttpClientProvider.createClient(
|
|
161
|
+
),
|
|
162
|
+
null,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
@Deprecated(
|
|
166
|
+
"""To be removed in a future release. See
|
|
167
|
+
https://github.com/facebook/react-native/pull/37798#pullrequestreview-1518338914"""
|
|
168
|
+
)
|
|
169
|
+
public interface CustomClientBuilder : com.facebook.react.modules.network.CustomClientBuilder
|
|
170
|
+
|
|
171
|
+
fun initialize() {
|
|
172
|
+
cookieJarContainer?.setCookieJar(JavaNetCookieJar(cookieHandler))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fun invalidate() {
|
|
176
|
+
shuttingDown = true
|
|
177
|
+
cancelAllRequests()
|
|
178
|
+
|
|
179
|
+
cookieHandler.destroy()
|
|
180
|
+
cookieJarContainer?.removeCookieJar()
|
|
181
|
+
|
|
182
|
+
requestBodyHandlers.clear()
|
|
183
|
+
responseHandlers.clear()
|
|
184
|
+
uriHandlers.clear()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
internal fun addUriHandler(handler: UriHandler) {
|
|
188
|
+
uriHandlers.add(handler)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
internal fun addRequestBodyHandler(handler: RequestBodyHandler) {
|
|
192
|
+
requestBodyHandlers.add(handler)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
internal fun addResponseHandler(handler: ResponseHandler) {
|
|
196
|
+
responseHandlers.add(handler)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
internal fun removeUriHandler(handler: UriHandler) {
|
|
200
|
+
uriHandlers.remove(handler)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
internal fun removeRequestBodyHandler(handler: RequestBodyHandler) {
|
|
204
|
+
requestBodyHandlers.remove(handler)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
internal fun removeResponseHandler(handler: ResponseHandler) {
|
|
208
|
+
responseHandlers.remove(handler)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private fun extractOrGenerateDevToolsRequestId(
|
|
212
|
+
data: ReadableMap?,
|
|
213
|
+
): String =
|
|
214
|
+
(if (
|
|
215
|
+
data != null &&
|
|
216
|
+
data.hasKey(REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID) &&
|
|
217
|
+
data.getType(REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID) == ReadableType.String
|
|
218
|
+
)
|
|
219
|
+
data.getString(REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID)
|
|
220
|
+
else null) ?: UUID.randomUUID().toString()
|
|
221
|
+
|
|
222
|
+
fun jsiSendRequest(
|
|
223
|
+
runtimeWrapper: WorkletRuntimeWrapper,
|
|
224
|
+
method: String,
|
|
225
|
+
url: String,
|
|
226
|
+
requestIdAsDouble: Double,
|
|
227
|
+
headers: ReadableArray?,
|
|
228
|
+
data: ReadableMap?,
|
|
229
|
+
responseType: String,
|
|
230
|
+
useIncrementalUpdates: Boolean,
|
|
231
|
+
timeoutAsDouble: Double,
|
|
232
|
+
withCredentials: Boolean,
|
|
233
|
+
) {
|
|
234
|
+
val requestId = requestIdAsDouble.toInt()
|
|
235
|
+
val timeout = timeoutAsDouble.toInt()
|
|
236
|
+
val devToolsRequestId = extractOrGenerateDevToolsRequestId(data)
|
|
237
|
+
try {
|
|
238
|
+
sendRequestInternalReal(
|
|
239
|
+
runtimeWrapper,
|
|
240
|
+
method,
|
|
241
|
+
url,
|
|
242
|
+
requestId,
|
|
243
|
+
headers,
|
|
244
|
+
data,
|
|
245
|
+
responseType,
|
|
246
|
+
useIncrementalUpdates,
|
|
247
|
+
timeout,
|
|
248
|
+
withCredentials,
|
|
249
|
+
devToolsRequestId,
|
|
250
|
+
)
|
|
251
|
+
} catch (th: Throwable) {
|
|
252
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
253
|
+
runtimeWrapper,
|
|
254
|
+
requestId,
|
|
255
|
+
devToolsRequestId,
|
|
256
|
+
th.message,
|
|
257
|
+
th,
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// @Deprecated("""sendRequestInternal is internal and will be made private in a future release.""")
|
|
263
|
+
// /** @param timeout value of 0 results in no timeout */
|
|
264
|
+
// public fun sendRequestInternal(
|
|
265
|
+
// runtimeWrapper: WorkletRuntimeWrapper,
|
|
266
|
+
// method: String,
|
|
267
|
+
// url: String?,
|
|
268
|
+
// requestId: Int,
|
|
269
|
+
// headers: ReadableArray?,
|
|
270
|
+
// data: ReadableMap?,
|
|
271
|
+
// responseType: String,
|
|
272
|
+
// useIncrementalUpdates: Boolean,
|
|
273
|
+
// timeout: Int,
|
|
274
|
+
// withCredentials: Boolean,
|
|
275
|
+
// ) {
|
|
276
|
+
// sendRequestInternalReal(
|
|
277
|
+
// runtimeWrapper,
|
|
278
|
+
// method,
|
|
279
|
+
// url,
|
|
280
|
+
// requestId,
|
|
281
|
+
// headers,
|
|
282
|
+
// data,
|
|
283
|
+
// responseType,
|
|
284
|
+
// useIncrementalUpdates,
|
|
285
|
+
// timeout,
|
|
286
|
+
// withCredentials,
|
|
287
|
+
// extractOrGenerateDevToolsRequestId(data),
|
|
288
|
+
// )
|
|
289
|
+
// }
|
|
290
|
+
|
|
291
|
+
/** @param timeout value of 0 results in no timeout */
|
|
292
|
+
private fun sendRequestInternalReal(
|
|
293
|
+
runtimeWrapper: WorkletRuntimeWrapper,
|
|
294
|
+
method: String,
|
|
295
|
+
url: String?,
|
|
296
|
+
requestId: Int,
|
|
297
|
+
headers: ReadableArray?,
|
|
298
|
+
data: ReadableMap?,
|
|
299
|
+
responseType: String,
|
|
300
|
+
useIncrementalUpdates: Boolean,
|
|
301
|
+
timeout: Int,
|
|
302
|
+
withCredentials: Boolean,
|
|
303
|
+
devToolsRequestId: String,
|
|
304
|
+
) {
|
|
305
|
+
try {
|
|
306
|
+
val uri = Uri.parse(url)
|
|
307
|
+
|
|
308
|
+
// Check if a handler is registered
|
|
309
|
+
for (handler in uriHandlers) {
|
|
310
|
+
if (handler.supports(uri, responseType)) {
|
|
311
|
+
val (res, rawBody) = handler.fetch(uri)
|
|
312
|
+
val encodedDataLength = res.toString().toByteArray().size
|
|
313
|
+
// fix: UriHandlers which are not using file:// scheme fail in whatwg-fetch at this line
|
|
314
|
+
// https://github.com/JakeChampion/fetch/blob/main/fetch.js#L547
|
|
315
|
+
val response =
|
|
316
|
+
Response.Builder()
|
|
317
|
+
.protocol(Protocol.HTTP_1_1)
|
|
318
|
+
.request(Request.Builder().url(url.orEmpty()).build())
|
|
319
|
+
.code(200)
|
|
320
|
+
.message("OK")
|
|
321
|
+
.build()
|
|
322
|
+
WorkletsNetworkEventUtil.onResponseReceived(
|
|
323
|
+
runtimeWrapper,
|
|
324
|
+
requestId,
|
|
325
|
+
devToolsRequestId,
|
|
326
|
+
url,
|
|
327
|
+
response,
|
|
328
|
+
)
|
|
329
|
+
WorkletsNetworkEventUtil.onDataReceived(
|
|
330
|
+
runtimeWrapper,
|
|
331
|
+
requestId,
|
|
332
|
+
devToolsRequestId,
|
|
333
|
+
res,
|
|
334
|
+
rawBody,
|
|
335
|
+
)
|
|
336
|
+
WorkletsNetworkEventUtil.onRequestSuccess(
|
|
337
|
+
runtimeWrapper,
|
|
338
|
+
requestId,
|
|
339
|
+
devToolsRequestId,
|
|
340
|
+
encodedDataLength.toLong(),
|
|
341
|
+
)
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
} catch (e: IOException) {
|
|
346
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
347
|
+
runtimeWrapper,
|
|
348
|
+
requestId,
|
|
349
|
+
devToolsRequestId,
|
|
350
|
+
e.message,
|
|
351
|
+
e,
|
|
352
|
+
)
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
val requestBuilder: Request.Builder
|
|
357
|
+
try {
|
|
358
|
+
requestBuilder = Request.Builder().url(url.orEmpty())
|
|
359
|
+
} catch (e: Exception) {
|
|
360
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
361
|
+
runtimeWrapper,
|
|
362
|
+
requestId,
|
|
363
|
+
devToolsRequestId,
|
|
364
|
+
e.message,
|
|
365
|
+
e,
|
|
366
|
+
)
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (requestId != 0) {
|
|
371
|
+
requestBuilder.tag(RequestId(runtimeWrapper.getRuntimeId(), requestId).hashCode())
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
val clientBuilder = client.newBuilder()
|
|
375
|
+
|
|
376
|
+
applyCustomBuilder(clientBuilder)
|
|
377
|
+
|
|
378
|
+
if (!withCredentials) {
|
|
379
|
+
clientBuilder.cookieJar(CookieJar.NO_COOKIES)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// If JS is listening for progress updates, install a ProgressResponseBody that intercepts the
|
|
383
|
+
// response and counts bytes received.
|
|
384
|
+
if (useIncrementalUpdates) {
|
|
385
|
+
clientBuilder.addNetworkInterceptor { chain ->
|
|
386
|
+
val originalResponse = chain.proceed(chain.request())
|
|
387
|
+
val originalResponseBody = checkNotNull(originalResponse.body())
|
|
388
|
+
val responseBody =
|
|
389
|
+
WorkletsProgressResponseBody(
|
|
390
|
+
originalResponseBody,
|
|
391
|
+
object : WorkletsProgressListener {
|
|
392
|
+
var last: Long = System.nanoTime()
|
|
393
|
+
|
|
394
|
+
override fun onProgress(bytesWritten: Long, contentLength: Long, done: Boolean) {
|
|
395
|
+
val now = System.nanoTime()
|
|
396
|
+
if (!done && !shouldDispatch(now, last)) {
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
if (responseType == "text") {
|
|
400
|
+
// For 'text' responses we continuously send response data with progress
|
|
401
|
+
// info to
|
|
402
|
+
// JS below, so no need to do anything here.
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
WorkletsNetworkEventUtil.onDataReceivedProgress(
|
|
406
|
+
runtimeWrapper,
|
|
407
|
+
requestId,
|
|
408
|
+
bytesWritten,
|
|
409
|
+
contentLength,
|
|
410
|
+
)
|
|
411
|
+
last = now
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
)
|
|
415
|
+
originalResponse.newBuilder().body(responseBody).build()
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// If the current timeout does not equal the passed in timeout, we need to clone the existing
|
|
420
|
+
// client and set the timeout explicitly on the clone. This is cheap as everything else is
|
|
421
|
+
// shared under the hood.
|
|
422
|
+
// See https://github.com/square/okhttp/wiki/Recipes#per-call-configuration for more information
|
|
423
|
+
if (timeout != client.callTimeoutMillis()) {
|
|
424
|
+
clientBuilder.callTimeout(timeout.toLong(), TimeUnit.MILLISECONDS)
|
|
425
|
+
}
|
|
426
|
+
val client = clientBuilder.build()
|
|
427
|
+
|
|
428
|
+
val requestHeaders = extractHeaders(headers, data)
|
|
429
|
+
if (requestHeaders == null) {
|
|
430
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
431
|
+
runtimeWrapper,
|
|
432
|
+
requestId,
|
|
433
|
+
devToolsRequestId,
|
|
434
|
+
"Unrecognized headers format",
|
|
435
|
+
null,
|
|
436
|
+
)
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
var contentType = requestHeaders[CONTENT_TYPE_HEADER_NAME]
|
|
440
|
+
val contentEncoding = requestHeaders[CONTENT_ENCODING_HEADER_NAME]
|
|
441
|
+
requestBuilder.headers(requestHeaders)
|
|
442
|
+
|
|
443
|
+
// Check if a handler is registered
|
|
444
|
+
var handler: RequestBodyHandler? = null
|
|
445
|
+
if (data != null) {
|
|
446
|
+
for (curHandler in requestBodyHandlers) {
|
|
447
|
+
if (curHandler.supports(data)) {
|
|
448
|
+
handler = curHandler
|
|
449
|
+
break
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
var requestBody: RequestBody?
|
|
455
|
+
when {
|
|
456
|
+
data == null || method.lowercase() == "get" || method.lowercase() == "head" ->
|
|
457
|
+
requestBody = WorkletsRequestBodyUtil.getEmptyBody(method)
|
|
458
|
+
handler != null -> requestBody = handler.toRequestBody(data, contentType)
|
|
459
|
+
data.hasKey(REQUEST_BODY_KEY_STRING) -> {
|
|
460
|
+
if (contentType == null) {
|
|
461
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
462
|
+
runtimeWrapper,
|
|
463
|
+
requestId,
|
|
464
|
+
devToolsRequestId,
|
|
465
|
+
"Payload is set but no content-type header specified",
|
|
466
|
+
null,
|
|
467
|
+
)
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
val body = data.getString(REQUEST_BODY_KEY_STRING)
|
|
471
|
+
val contentMediaType = MediaType.parse(contentType)
|
|
472
|
+
if (WorkletsRequestBodyUtil.isGzipEncoding(contentEncoding)) {
|
|
473
|
+
requestBody = null
|
|
474
|
+
if (contentMediaType != null && body != null) {
|
|
475
|
+
requestBody = WorkletsRequestBodyUtil.createGzip(contentMediaType, body)
|
|
476
|
+
}
|
|
477
|
+
if (requestBody == null) {
|
|
478
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
479
|
+
runtimeWrapper,
|
|
480
|
+
requestId,
|
|
481
|
+
devToolsRequestId,
|
|
482
|
+
"Failed to gzip request body",
|
|
483
|
+
null,
|
|
484
|
+
)
|
|
485
|
+
return
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
// Use getBytes() to convert the body into a byte[], preventing okhttp from
|
|
489
|
+
// appending the character set to the Content-Type header when otherwise unspecified
|
|
490
|
+
// https://github.com/facebook/react-native/issues/8237
|
|
491
|
+
val charset =
|
|
492
|
+
if (contentMediaType == null) {
|
|
493
|
+
StandardCharsets.UTF_8
|
|
494
|
+
} else {
|
|
495
|
+
checkNotNull(contentMediaType.charset(StandardCharsets.UTF_8))
|
|
496
|
+
}
|
|
497
|
+
if (body == null) {
|
|
498
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
499
|
+
runtimeWrapper,
|
|
500
|
+
requestId,
|
|
501
|
+
devToolsRequestId,
|
|
502
|
+
"Received request but body was empty",
|
|
503
|
+
null,
|
|
504
|
+
)
|
|
505
|
+
return
|
|
506
|
+
}
|
|
507
|
+
@Suppress("DEPRECATION")
|
|
508
|
+
requestBody = RequestBody.create(contentMediaType, body.toByteArray(charset))
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
data.hasKey(REQUEST_BODY_KEY_BASE64) -> {
|
|
512
|
+
if (contentType == null) {
|
|
513
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
514
|
+
runtimeWrapper,
|
|
515
|
+
requestId,
|
|
516
|
+
devToolsRequestId,
|
|
517
|
+
"Payload is set but no content-type header specified",
|
|
518
|
+
null,
|
|
519
|
+
)
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
val base64String = data.getString(REQUEST_BODY_KEY_BASE64)
|
|
523
|
+
checkNotNull(base64String)
|
|
524
|
+
|
|
525
|
+
val contentMediaType = MediaType.parse(contentType)
|
|
526
|
+
if (contentMediaType == null) {
|
|
527
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
528
|
+
runtimeWrapper,
|
|
529
|
+
requestId,
|
|
530
|
+
devToolsRequestId,
|
|
531
|
+
"Invalid content type specified: $contentType",
|
|
532
|
+
null,
|
|
533
|
+
)
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
val base64DecodedString = ByteString.decodeBase64(base64String)
|
|
537
|
+
if (base64DecodedString == null) {
|
|
538
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
539
|
+
runtimeWrapper,
|
|
540
|
+
requestId,
|
|
541
|
+
devToolsRequestId,
|
|
542
|
+
"Request body base64 string was invalid",
|
|
543
|
+
null,
|
|
544
|
+
)
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
@Suppress("DEPRECATION")
|
|
548
|
+
requestBody = RequestBody.create(contentMediaType, base64DecodedString)
|
|
549
|
+
}
|
|
550
|
+
data.hasKey(REQUEST_BODY_KEY_URI) -> {
|
|
551
|
+
if (contentType == null) {
|
|
552
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
553
|
+
runtimeWrapper,
|
|
554
|
+
requestId,
|
|
555
|
+
devToolsRequestId,
|
|
556
|
+
"Payload is set but no content-type header specified",
|
|
557
|
+
null,
|
|
558
|
+
)
|
|
559
|
+
return
|
|
560
|
+
}
|
|
561
|
+
val uri = data.getString(REQUEST_BODY_KEY_URI)
|
|
562
|
+
if (uri == null) {
|
|
563
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
564
|
+
runtimeWrapper,
|
|
565
|
+
requestId,
|
|
566
|
+
devToolsRequestId,
|
|
567
|
+
"Request body URI field was set but null",
|
|
568
|
+
null,
|
|
569
|
+
)
|
|
570
|
+
return
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// TODO: Fix me
|
|
574
|
+
val fileInputStream = null
|
|
575
|
+
// WorkletsRequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri)
|
|
576
|
+
if (fileInputStream == null) {
|
|
577
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
578
|
+
runtimeWrapper,
|
|
579
|
+
requestId,
|
|
580
|
+
devToolsRequestId,
|
|
581
|
+
"Could not retrieve file for uri $uri",
|
|
582
|
+
null,
|
|
583
|
+
)
|
|
584
|
+
return
|
|
585
|
+
}
|
|
586
|
+
requestBody = WorkletsRequestBodyUtil.create(MediaType.parse(contentType), fileInputStream)
|
|
587
|
+
}
|
|
588
|
+
data.hasKey(REQUEST_BODY_KEY_FORMDATA) -> {
|
|
589
|
+
if (contentType == null) {
|
|
590
|
+
contentType = "multipart/form-data"
|
|
591
|
+
}
|
|
592
|
+
val parts = data.getArray(REQUEST_BODY_KEY_FORMDATA)
|
|
593
|
+
if (parts == null) {
|
|
594
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
595
|
+
runtimeWrapper,
|
|
596
|
+
requestId,
|
|
597
|
+
devToolsRequestId,
|
|
598
|
+
"Received request but form data was empty",
|
|
599
|
+
null,
|
|
600
|
+
)
|
|
601
|
+
return
|
|
602
|
+
}
|
|
603
|
+
val multipartBuilder =
|
|
604
|
+
constructMultipartBody(runtimeWrapper, parts, contentType, requestId, devToolsRequestId) ?: return
|
|
605
|
+
requestBody = multipartBuilder.build()
|
|
606
|
+
}
|
|
607
|
+
else -> {
|
|
608
|
+
// Nothing in data payload, at least nothing we could understand anyway.
|
|
609
|
+
requestBody = WorkletsRequestBodyUtil.getEmptyBody(method)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
requestBuilder.method(method, wrapRequestBodyWithProgressEmitter(runtimeWrapper, requestBody, requestId))
|
|
614
|
+
|
|
615
|
+
addRequest(runtimeWrapper.getRuntimeId(), requestId)
|
|
616
|
+
val request = requestBuilder.build()
|
|
617
|
+
WorkletsNetworkEventUtil.onCreateRequest(devToolsRequestId, request)
|
|
618
|
+
|
|
619
|
+
client
|
|
620
|
+
.newCall(request)
|
|
621
|
+
.enqueue(
|
|
622
|
+
object : Callback {
|
|
623
|
+
override fun onFailure(call: Call, e: IOException) {
|
|
624
|
+
if (shuttingDown) {
|
|
625
|
+
return
|
|
626
|
+
}
|
|
627
|
+
removeRequest(runtimeWrapper.getRuntimeId(), requestId)
|
|
628
|
+
val errorMessage =
|
|
629
|
+
e.message ?: ("Error while executing request: ${e.javaClass.simpleName}")
|
|
630
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
631
|
+
runtimeWrapper,
|
|
632
|
+
requestId,
|
|
633
|
+
devToolsRequestId,
|
|
634
|
+
errorMessage,
|
|
635
|
+
e,
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
@Throws(IOException::class)
|
|
640
|
+
override fun onResponse(call: Call, response: Response) {
|
|
641
|
+
if (shuttingDown) {
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
removeRequest(runtimeWrapper.getRuntimeId(), requestId)
|
|
645
|
+
// Before we touch the body send headers to JS
|
|
646
|
+
WorkletsNetworkEventUtil.onResponseReceived(
|
|
647
|
+
runtimeWrapper,
|
|
648
|
+
requestId,
|
|
649
|
+
devToolsRequestId,
|
|
650
|
+
url,
|
|
651
|
+
response,
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
// OkHttp implements something called transparent gzip, which mean that it will
|
|
656
|
+
// automatically add the Accept-Encoding gzip header and handle decoding
|
|
657
|
+
// internally.
|
|
658
|
+
// The issue is that it won't handle decoding if the user provides a
|
|
659
|
+
// Accept-Encoding
|
|
660
|
+
// header. This is also undesirable considering that iOS does handle the decoding
|
|
661
|
+
// even
|
|
662
|
+
// when the header is provided. To make sure this works in all cases, handle gzip
|
|
663
|
+
// body
|
|
664
|
+
// here also. This works fine since OKHttp will remove the Content-Encoding header
|
|
665
|
+
// if
|
|
666
|
+
// it used transparent gzip.
|
|
667
|
+
// See
|
|
668
|
+
// https://github.com/square/okhttp/blob/5b37cda9e00626f43acf354df145fd452c3031f1/okhttp/src/main/java/okhttp3/internal/http/BridgeInterceptor.java#L76-L111
|
|
669
|
+
var responseBody: ResponseBody? = response.body()
|
|
670
|
+
if (responseBody == null) {
|
|
671
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
672
|
+
// reactApplicationContext,
|
|
673
|
+
runtimeWrapper,
|
|
674
|
+
requestId,
|
|
675
|
+
devToolsRequestId,
|
|
676
|
+
"Response body is null",
|
|
677
|
+
null,
|
|
678
|
+
)
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
if ("gzip".equals(response.header("Content-Encoding"), ignoreCase = true)) {
|
|
682
|
+
val gzipSource = GzipSource(responseBody.source())
|
|
683
|
+
val parsedContentType = response.header("Content-Type")?.let(MediaType::parse)
|
|
684
|
+
responseBody =
|
|
685
|
+
@Suppress("DEPRECATION")
|
|
686
|
+
ResponseBody.create(
|
|
687
|
+
parsedContentType,
|
|
688
|
+
-1L,
|
|
689
|
+
Okio.buffer(gzipSource),
|
|
690
|
+
)
|
|
691
|
+
}
|
|
692
|
+
// To satisfy the compiler, this is already checked above
|
|
693
|
+
checkNotNull(responseBody)
|
|
694
|
+
// Check if a handler is registered
|
|
695
|
+
for (responseHandler in responseHandlers) {
|
|
696
|
+
if (responseHandler.supports(responseType)) {
|
|
697
|
+
val responseData = responseBody.bytes()
|
|
698
|
+
val res = responseHandler.toResponseData(responseData)
|
|
699
|
+
WorkletsNetworkEventUtil.onDataReceived(
|
|
700
|
+
runtimeWrapper,
|
|
701
|
+
requestId,
|
|
702
|
+
devToolsRequestId,
|
|
703
|
+
res,
|
|
704
|
+
responseData,
|
|
705
|
+
)
|
|
706
|
+
WorkletsNetworkEventUtil.onRequestSuccess(
|
|
707
|
+
runtimeWrapper,
|
|
708
|
+
requestId,
|
|
709
|
+
devToolsRequestId,
|
|
710
|
+
responseBody.contentLength(),
|
|
711
|
+
)
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// If JS wants progress updates during the download, and it requested a text
|
|
717
|
+
// response,
|
|
718
|
+
// periodically send response data updates to JS.
|
|
719
|
+
if (useIncrementalUpdates && responseType == "text") {
|
|
720
|
+
readWithProgress(runtimeWrapper, requestId, devToolsRequestId, responseBody)
|
|
721
|
+
WorkletsNetworkEventUtil.onRequestSuccess(
|
|
722
|
+
runtimeWrapper,
|
|
723
|
+
requestId,
|
|
724
|
+
devToolsRequestId,
|
|
725
|
+
responseBody.contentLength(),
|
|
726
|
+
)
|
|
727
|
+
return
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Otherwise send the data in one big chunk, in the format that JS requested.
|
|
731
|
+
var responseString: String? = ""
|
|
732
|
+
if (responseType == "text") {
|
|
733
|
+
try {
|
|
734
|
+
responseString = responseBody.string()
|
|
735
|
+
} catch (e: IOException) {
|
|
736
|
+
if (response.request().method().equals("HEAD", ignoreCase = true)) {
|
|
737
|
+
// The request is an `HEAD` and the body is empty,
|
|
738
|
+
// the OkHttp will produce an exception.
|
|
739
|
+
// Ignore the exception to not invalidate the request in the
|
|
740
|
+
// Javascript layer.
|
|
741
|
+
// Introduced to fix issue #7463.
|
|
742
|
+
} else {
|
|
743
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
744
|
+
runtimeWrapper,
|
|
745
|
+
requestId,
|
|
746
|
+
devToolsRequestId,
|
|
747
|
+
e.message,
|
|
748
|
+
e,
|
|
749
|
+
)
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
} else if (responseType == "base64") {
|
|
753
|
+
responseString = Base64.encodeToString(responseBody.bytes(), Base64.NO_WRAP)
|
|
754
|
+
}
|
|
755
|
+
WorkletsNetworkEventUtil.onDataReceived(
|
|
756
|
+
runtimeWrapper,
|
|
757
|
+
requestId,
|
|
758
|
+
devToolsRequestId,
|
|
759
|
+
responseString,
|
|
760
|
+
responseType,
|
|
761
|
+
)
|
|
762
|
+
WorkletsNetworkEventUtil.onRequestSuccess(
|
|
763
|
+
runtimeWrapper,
|
|
764
|
+
requestId,
|
|
765
|
+
devToolsRequestId,
|
|
766
|
+
responseBody.contentLength(),
|
|
767
|
+
)
|
|
768
|
+
} catch (e: IOException) {
|
|
769
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
770
|
+
runtimeWrapper,
|
|
771
|
+
requestId,
|
|
772
|
+
devToolsRequestId,
|
|
773
|
+
e.message,
|
|
774
|
+
e,
|
|
775
|
+
)
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
private fun wrapRequestBodyWithProgressEmitter(
|
|
783
|
+
runtimeWrapper: WorkletRuntimeWrapper,
|
|
784
|
+
requestBody: RequestBody?,
|
|
785
|
+
requestId: Int,
|
|
786
|
+
): RequestBody? {
|
|
787
|
+
if (requestBody == null) {
|
|
788
|
+
return null
|
|
789
|
+
}
|
|
790
|
+
// val reactApplicationContext = getReactApplicationContextIfActiveOrWarn()
|
|
791
|
+
return WorkletsRequestBodyUtil.createProgressRequest(
|
|
792
|
+
requestBody,
|
|
793
|
+
object : WorkletsProgressListener {
|
|
794
|
+
var last: Long = System.nanoTime()
|
|
795
|
+
|
|
796
|
+
override fun onProgress(bytesWritten: Long, contentLength: Long, done: Boolean) {
|
|
797
|
+
val now = System.nanoTime()
|
|
798
|
+
if (done || shouldDispatch(now, last)) {
|
|
799
|
+
WorkletsNetworkEventUtil.onDataSend(
|
|
800
|
+
runtimeWrapper,
|
|
801
|
+
requestId,
|
|
802
|
+
bytesWritten,
|
|
803
|
+
contentLength,
|
|
804
|
+
)
|
|
805
|
+
last = now
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
@Throws(IOException::class)
|
|
813
|
+
private fun readWithProgress(
|
|
814
|
+
runtimeWrapper: WorkletRuntimeWrapper,
|
|
815
|
+
requestId: Int,
|
|
816
|
+
devToolsRequestId: String,
|
|
817
|
+
responseBody: ResponseBody,
|
|
818
|
+
) {
|
|
819
|
+
var totalBytesRead: Long = -1
|
|
820
|
+
var contentLength: Long = -1
|
|
821
|
+
try {
|
|
822
|
+
val progressResponseBody = responseBody as WorkletsProgressResponseBody
|
|
823
|
+
totalBytesRead = progressResponseBody.totalBytesRead()
|
|
824
|
+
contentLength = progressResponseBody.contentLength()
|
|
825
|
+
} catch (e: ClassCastException) {
|
|
826
|
+
// Ignore
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
val charset =
|
|
830
|
+
if (responseBody.contentType() == null) {
|
|
831
|
+
StandardCharsets.UTF_8
|
|
832
|
+
} else {
|
|
833
|
+
checkNotNull(responseBody.contentType()?.charset(StandardCharsets.UTF_8)) {
|
|
834
|
+
"Null character set for Content-Type: ${responseBody.contentType()}"
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
val streamDecoder = WorkletsProgressiveStringDecoder(charset)
|
|
838
|
+
val inputStream = responseBody.byteStream()
|
|
839
|
+
try {
|
|
840
|
+
val buffer = ByteArray(MAX_CHUNK_SIZE_BETWEEN_FLUSHES)
|
|
841
|
+
var read: Int
|
|
842
|
+
// val reactApplicationContext = getReactApplicationContextIfActiveOrWarn()
|
|
843
|
+
while ((inputStream.read(buffer).also { read = it }) != -1) {
|
|
844
|
+
WorkletsNetworkEventUtil.onIncrementalDataReceived(
|
|
845
|
+
runtimeWrapper,
|
|
846
|
+
requestId,
|
|
847
|
+
devToolsRequestId,
|
|
848
|
+
streamDecoder.decodeNext(buffer, read),
|
|
849
|
+
totalBytesRead,
|
|
850
|
+
contentLength,
|
|
851
|
+
)
|
|
852
|
+
}
|
|
853
|
+
} finally {
|
|
854
|
+
inputStream.close()
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
@Synchronized
|
|
859
|
+
private fun addRequest(runtimeId: Int, requestId: Int) {
|
|
860
|
+
val requests = requestIds[runtimeId];
|
|
861
|
+
if (requests == null) {
|
|
862
|
+
requestIds[runtimeId] = hashSetOf(requestId)
|
|
863
|
+
} else {
|
|
864
|
+
requests.add(requestId)
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
@Synchronized
|
|
870
|
+
private fun removeRequest(runtimeId: Int, requestId: Int) {
|
|
871
|
+
val requests = requestIds[runtimeId];
|
|
872
|
+
requests?.remove(requestId)
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
@Synchronized
|
|
876
|
+
private fun cancelAllRequests() {
|
|
877
|
+
for (runtimeId in requestIds) {
|
|
878
|
+
for (requestId in runtimeId.value) {
|
|
879
|
+
cancelRequest(runtimeId.key, requestId)
|
|
880
|
+
removeRequest(runtimeId.key, requestId)
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
requestIds.clear()
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
fun jsiAbortRequest(runtimeId: Int, requestIdAsDouble: Double) {
|
|
887
|
+
val requestId = requestIdAsDouble.toInt()
|
|
888
|
+
cancelRequest(runtimeId, requestId)
|
|
889
|
+
removeRequest(runtimeId, requestId)
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
private fun cancelRequest(runtimeId: Int, requestId: Int) {
|
|
893
|
+
WorkletsOkHttpCallUtil.cancelTag(client, RequestId(runtimeId, requestId).hashCode())
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
fun jsiClearCookies(callback: com.facebook.react.bridge.Callback) {
|
|
897
|
+
cookieHandler.clearCookies(callback)
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
public fun addListener(eventName: String?): Unit = Unit
|
|
901
|
+
|
|
902
|
+
public fun removeListeners(count: Double): Unit = Unit
|
|
903
|
+
|
|
904
|
+
private fun constructMultipartBody(
|
|
905
|
+
runtimeWrapper: WorkletRuntimeWrapper,
|
|
906
|
+
body: ReadableArray,
|
|
907
|
+
contentType: String,
|
|
908
|
+
requestId: Int,
|
|
909
|
+
devToolsRequestId: String,
|
|
910
|
+
): MultipartBody.Builder? {
|
|
911
|
+
// val reactApplicationContext = getReactApplicationContextIfActiveOrWarn()
|
|
912
|
+
val multipartBuilder = MultipartBody.Builder()
|
|
913
|
+
val mediaType = MediaType.parse(contentType)
|
|
914
|
+
if (mediaType == null) {
|
|
915
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
916
|
+
runtimeWrapper,
|
|
917
|
+
requestId,
|
|
918
|
+
devToolsRequestId,
|
|
919
|
+
"Invalid media type.",
|
|
920
|
+
null,
|
|
921
|
+
)
|
|
922
|
+
return null
|
|
923
|
+
}
|
|
924
|
+
multipartBuilder.setType(mediaType)
|
|
925
|
+
|
|
926
|
+
for (i in 0 until body.size()) {
|
|
927
|
+
val bodyPart = body.getMap(i)
|
|
928
|
+
if (bodyPart == null) {
|
|
929
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
930
|
+
runtimeWrapper,
|
|
931
|
+
requestId,
|
|
932
|
+
devToolsRequestId,
|
|
933
|
+
"Unrecognized FormData part.",
|
|
934
|
+
null,
|
|
935
|
+
)
|
|
936
|
+
return null
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// Determine part's content type.
|
|
940
|
+
val headersArray = bodyPart.getArray("headers")
|
|
941
|
+
var headers = extractHeaders(headersArray, null)
|
|
942
|
+
if (headers == null) {
|
|
943
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
944
|
+
runtimeWrapper,
|
|
945
|
+
requestId,
|
|
946
|
+
devToolsRequestId,
|
|
947
|
+
"Missing or invalid header format for FormData part.",
|
|
948
|
+
null,
|
|
949
|
+
)
|
|
950
|
+
return null
|
|
951
|
+
}
|
|
952
|
+
var partContentType: MediaType? = null
|
|
953
|
+
val partContentTypeStr = headers[CONTENT_TYPE_HEADER_NAME]
|
|
954
|
+
if (partContentTypeStr != null) {
|
|
955
|
+
partContentType = MediaType.parse(partContentTypeStr)
|
|
956
|
+
// Remove the content-type header because MultipartBuilder gets it explicitly as an
|
|
957
|
+
// argument and doesn't expect it in the headers array.
|
|
958
|
+
headers = headers.newBuilder().removeAll(CONTENT_TYPE_HEADER_NAME).build()
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (
|
|
962
|
+
bodyPart.hasKey(REQUEST_BODY_KEY_STRING) &&
|
|
963
|
+
bodyPart.getString(REQUEST_BODY_KEY_STRING) != null
|
|
964
|
+
) {
|
|
965
|
+
val bodyValue = bodyPart.getString(REQUEST_BODY_KEY_STRING).orEmpty()
|
|
966
|
+
@Suppress("DEPRECATION")
|
|
967
|
+
multipartBuilder.addPart(headers, RequestBody.create(partContentType, bodyValue))
|
|
968
|
+
} else if (
|
|
969
|
+
bodyPart.hasKey(REQUEST_BODY_KEY_URI) && bodyPart.getString(REQUEST_BODY_KEY_URI) != null
|
|
970
|
+
) {
|
|
971
|
+
if (partContentType == null) {
|
|
972
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
973
|
+
runtimeWrapper,
|
|
974
|
+
requestId,
|
|
975
|
+
devToolsRequestId,
|
|
976
|
+
"Binary FormData part needs a content-type header.",
|
|
977
|
+
null,
|
|
978
|
+
)
|
|
979
|
+
return null
|
|
980
|
+
}
|
|
981
|
+
val fileContentUriStr = bodyPart.getString(REQUEST_BODY_KEY_URI)
|
|
982
|
+
if (fileContentUriStr == null) {
|
|
983
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
984
|
+
runtimeWrapper,
|
|
985
|
+
requestId,
|
|
986
|
+
devToolsRequestId,
|
|
987
|
+
"Body must have a valid file uri",
|
|
988
|
+
null,
|
|
989
|
+
)
|
|
990
|
+
return null
|
|
991
|
+
}
|
|
992
|
+
// TODO: Fix me
|
|
993
|
+
val fileInputStream = null
|
|
994
|
+
// WorkletsRequestBodyUtil.getFileInputStream(getReactApplicationContext(), fileContentUriStr)
|
|
995
|
+
if (fileInputStream == null) {
|
|
996
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
997
|
+
runtimeWrapper,
|
|
998
|
+
requestId,
|
|
999
|
+
devToolsRequestId,
|
|
1000
|
+
"Could not retrieve file for uri $fileContentUriStr",
|
|
1001
|
+
null,
|
|
1002
|
+
)
|
|
1003
|
+
return null
|
|
1004
|
+
}
|
|
1005
|
+
multipartBuilder.addPart(headers, WorkletsRequestBodyUtil.create(partContentType, fileInputStream))
|
|
1006
|
+
} else {
|
|
1007
|
+
WorkletsNetworkEventUtil.onRequestError(
|
|
1008
|
+
runtimeWrapper,
|
|
1009
|
+
requestId,
|
|
1010
|
+
devToolsRequestId,
|
|
1011
|
+
"Unrecognized FormData part.",
|
|
1012
|
+
null,
|
|
1013
|
+
)
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return multipartBuilder
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Extracts the headers from the Array. If the format is invalid, this method will return null.
|
|
1021
|
+
*/
|
|
1022
|
+
private fun extractHeaders(headersArray: ReadableArray?, requestData: ReadableMap?): Headers? {
|
|
1023
|
+
if (headersArray == null) {
|
|
1024
|
+
return null
|
|
1025
|
+
}
|
|
1026
|
+
val headersBuilder = Headers.Builder()
|
|
1027
|
+
for (headersIdx in 0 until headersArray.size()) {
|
|
1028
|
+
val header = headersArray.getArray(headersIdx)
|
|
1029
|
+
if (header == null || header.size() != 2) {
|
|
1030
|
+
return null
|
|
1031
|
+
}
|
|
1032
|
+
var headerName: String? = header.getString(0)
|
|
1033
|
+
if (headerName != null) {
|
|
1034
|
+
headerName = WorkletsHeaderUtil.stripHeaderName(headerName)
|
|
1035
|
+
}
|
|
1036
|
+
val headerValue = header.getString(1)
|
|
1037
|
+
if (headerName == null || headerValue == null) {
|
|
1038
|
+
return null
|
|
1039
|
+
}
|
|
1040
|
+
headersBuilder.addUnsafeNonAscii(headerName, headerValue)
|
|
1041
|
+
}
|
|
1042
|
+
if (headersBuilder[USER_AGENT_HEADER_NAME] == null && defaultUserAgent != null) {
|
|
1043
|
+
headersBuilder.add(USER_AGENT_HEADER_NAME, defaultUserAgent)
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Sanitize content encoding header, supported only when request specify payload as string
|
|
1047
|
+
val isGzipSupported = requestData?.hasKey(REQUEST_BODY_KEY_STRING) == true
|
|
1048
|
+
if (!isGzipSupported) {
|
|
1049
|
+
headersBuilder.removeAll(CONTENT_ENCODING_HEADER_NAME)
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return headersBuilder.build()
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
public companion object {
|
|
1056
|
+
// public const val NAME: String = NativeNetworkingAndroidSpec.NAME
|
|
1057
|
+
// private const val TAG: String = NativeNetworkingAndroidSpec.NAME
|
|
1058
|
+
private const val CONTENT_ENCODING_HEADER_NAME = "content-encoding"
|
|
1059
|
+
private const val CONTENT_TYPE_HEADER_NAME = "content-type"
|
|
1060
|
+
private const val REQUEST_BODY_KEY_STRING = "string"
|
|
1061
|
+
private const val REQUEST_BODY_KEY_URI = "uri"
|
|
1062
|
+
private const val REQUEST_BODY_KEY_FORMDATA = "formData"
|
|
1063
|
+
private const val REQUEST_BODY_KEY_BASE64 = "base64"
|
|
1064
|
+
private const val REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID = "devToolsRequestId"
|
|
1065
|
+
private const val USER_AGENT_HEADER_NAME = "user-agent"
|
|
1066
|
+
private const val CHUNK_TIMEOUT_NS = 100 * 1_000_000 // 100ms
|
|
1067
|
+
private const val MAX_CHUNK_SIZE_BETWEEN_FLUSHES = 8 * 1_024 // 8K
|
|
1068
|
+
|
|
1069
|
+
private var customClientBuilder: com.facebook.react.modules.network.CustomClientBuilder? = null
|
|
1070
|
+
|
|
1071
|
+
@JvmStatic
|
|
1072
|
+
public fun setCustomClientBuilder(
|
|
1073
|
+
ccb: com.facebook.react.modules.network.CustomClientBuilder?
|
|
1074
|
+
) {
|
|
1075
|
+
customClientBuilder = ccb
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
private fun applyCustomBuilder(builder: OkHttpClient.Builder) {
|
|
1079
|
+
customClientBuilder?.apply(builder)
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
private fun shouldDispatch(now: Long, last: Long): Boolean = last + CHUNK_TIMEOUT_NS < now
|
|
1083
|
+
}
|
|
1084
|
+
}
|