react-native-worklets 0.7.1 → 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.
Files changed (123) hide show
  1. package/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp +32 -28
  2. package/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h +13 -5
  3. package/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.cpp +7 -5
  4. package/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.h +5 -4
  5. package/Common/cpp/worklets/Resources/SynchronizableUnpacker.cpp +5 -5
  6. package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.cpp +42 -19
  7. package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.h +2 -0
  8. package/Common/cpp/worklets/Tools/Defs.h +2 -2
  9. package/Common/cpp/worklets/Tools/ScriptBuffer.h +34 -0
  10. package/Common/cpp/worklets/WorkletRuntime/RuntimeBindings.h +24 -0
  11. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp +11 -6
  12. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.cpp +82 -0
  13. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.h +12 -0
  14. package/RNWorklets.podspec +16 -14
  15. package/android/CMakeLists.txt +8 -2
  16. package/android/build.gradle +92 -56
  17. package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.cpp +67 -0
  18. package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.h +48 -0
  19. package/android/src/main/cpp/worklets/android/JWorkletRuntimeWrapper.cpp +52 -0
  20. package/android/src/main/cpp/worklets/android/JWorkletRuntimeWrapper.h +43 -0
  21. package/android/src/main/cpp/worklets/android/WorkletsModule.cpp +115 -19
  22. package/android/src/main/cpp/worklets/android/WorkletsModule.h +11 -13
  23. package/android/src/main/cpp/worklets/android/WorkletsOnLoad.cpp +6 -0
  24. package/android/src/main/java/com/swmansion/worklets/ScriptBufferWrapper.java +88 -0
  25. package/android/src/networking/com/swmansion/worklets/WorkletRuntimeWrapper.kt +23 -0
  26. package/android/src/networking/com/swmansion/worklets/WorkletsHeaderUtil.kt +30 -0
  27. package/android/src/{legacyBundling → networking}/com/swmansion/worklets/WorkletsModule.java +52 -2
  28. package/android/src/networking/com/swmansion/worklets/WorkletsNetworkEventUtil.kt +268 -0
  29. package/android/src/networking/com/swmansion/worklets/WorkletsNetworking.kt +1084 -0
  30. package/android/src/networking/com/swmansion/worklets/WorkletsOkHttpCallUtil.kt +37 -0
  31. package/android/src/networking/com/swmansion/worklets/WorkletsProgressListener.kt +9 -0
  32. package/android/src/networking/com/swmansion/worklets/WorkletsProgressRequestBody.kt +98 -0
  33. package/android/src/networking/com/swmansion/worklets/WorkletsProgressResponseBody.kt +57 -0
  34. package/android/src/networking/com/swmansion/worklets/WorkletsProgressiveStringDecoder.kt +82 -0
  35. package/android/src/networking/com/swmansion/worklets/WorkletsRequestBodyUtil.kt +177 -0
  36. package/android/src/{experimentalBundling → no-networking}/com/swmansion/worklets/WorkletsModule.java +10 -15
  37. package/apple/worklets/apple/Networking/WorkletsNetworking.h +22 -0
  38. package/apple/worklets/apple/Networking/WorkletsNetworking.mm +706 -0
  39. package/apple/worklets/apple/WorkletsModule.mm +56 -17
  40. package/bundleMode/index.js +2 -6
  41. package/compatibility.json +4 -1
  42. package/lib/module/WorkletsModule/NativeWorklets.native.js +8 -2
  43. package/lib/module/WorkletsModule/NativeWorklets.native.js.map +1 -1
  44. package/lib/module/bundleMode/metroOverrides.native.js +115 -0
  45. package/lib/module/bundleMode/metroOverrides.native.js.map +1 -0
  46. package/lib/module/bundleMode/network.native.js +41 -0
  47. package/lib/module/bundleMode/network.native.js.map +1 -0
  48. package/lib/module/debug/jsVersion.js +1 -1
  49. package/lib/module/debug/jsVersion.js.map +1 -1
  50. package/lib/module/featureFlags/staticFlags.json +2 -0
  51. package/lib/module/featureFlags/types.js +3 -1
  52. package/lib/module/featureFlags/types.js.map +1 -1
  53. package/lib/module/index.js +4 -2
  54. package/lib/module/index.js.map +1 -1
  55. package/lib/module/initializers/initializers.native.js +24 -50
  56. package/lib/module/initializers/initializers.native.js.map +1 -1
  57. package/lib/module/initializers/workletRuntimeEntry.native.js +3 -3
  58. package/lib/module/initializers/workletRuntimeEntry.native.js.map +1 -1
  59. package/lib/module/memory/bundleUnpacker.native.js +2 -2
  60. package/lib/module/memory/bundleUnpacker.native.js.map +1 -1
  61. package/lib/module/memory/serializable.native.js +3 -3
  62. package/lib/module/memory/serializable.native.js.map +1 -1
  63. package/lib/module/memory/synchronizableUnpacker.native.js +3 -3
  64. package/lib/module/memory/synchronizableUnpacker.native.js.map +1 -1
  65. package/lib/module/platformChecker.js +2 -2
  66. package/lib/module/platformChecker.js.map +1 -1
  67. package/lib/module/runtimeKind.js +51 -0
  68. package/lib/module/runtimeKind.js.map +1 -1
  69. package/lib/module/runtimes.js +3 -0
  70. package/lib/module/runtimes.js.map +1 -1
  71. package/lib/module/runtimes.native.js +34 -3
  72. package/lib/module/runtimes.native.js.map +1 -1
  73. package/lib/module/threads.native.js +2 -2
  74. package/lib/module/threads.native.js.map +1 -1
  75. package/lib/typescript/WorkletsModule/NativeWorklets.native.d.ts.map +1 -1
  76. package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts +2 -1
  77. package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts.map +1 -1
  78. package/lib/typescript/bundleMode/metroOverrides.native.d.ts +28 -0
  79. package/lib/typescript/bundleMode/metroOverrides.native.d.ts.map +1 -0
  80. package/lib/typescript/bundleMode/network.native.d.ts +7 -0
  81. package/lib/typescript/bundleMode/network.native.d.ts.map +1 -0
  82. package/lib/typescript/debug/jsVersion.d.ts +1 -1
  83. package/lib/typescript/debug/jsVersion.d.ts.map +1 -1
  84. package/lib/typescript/featureFlags/types.d.ts +3 -1
  85. package/lib/typescript/featureFlags/types.d.ts.map +1 -1
  86. package/lib/typescript/index.d.ts +2 -2
  87. package/lib/typescript/index.d.ts.map +1 -1
  88. package/lib/typescript/initializers/initializers.native.d.ts +1 -0
  89. package/lib/typescript/initializers/initializers.native.d.ts.map +1 -1
  90. package/lib/typescript/initializers/workletRuntimeEntry.native.d.ts +1 -1
  91. package/lib/typescript/memory/bundleUnpacker.native.d.ts.map +1 -1
  92. package/lib/typescript/memory/synchronizableUnpacker.native.d.ts.map +1 -1
  93. package/lib/typescript/platformChecker.d.ts.map +1 -1
  94. package/lib/typescript/runtimeKind.d.ts +31 -0
  95. package/lib/typescript/runtimeKind.d.ts.map +1 -1
  96. package/lib/typescript/runtimes.d.ts +1 -0
  97. package/lib/typescript/runtimes.d.ts.map +1 -1
  98. package/lib/typescript/runtimes.native.d.ts +20 -2
  99. package/lib/typescript/runtimes.native.d.ts.map +1 -1
  100. package/lib/typescript/threads.native.d.ts +1 -1
  101. package/package.json +17 -6
  102. package/plugin/index.d.ts +109 -0
  103. package/plugin/index.js +59 -9
  104. package/scripts/worklets_utils.rb +21 -5
  105. package/src/WorkletsModule/NativeWorklets.native.ts +14 -4
  106. package/src/WorkletsModule/workletsModuleProxy.ts +6 -3
  107. package/src/bundleMode/metroOverrides.native.ts +151 -0
  108. package/src/bundleMode/network.native.ts +59 -0
  109. package/src/debug/jsVersion.ts +1 -1
  110. package/src/featureFlags/staticFlags.json +2 -0
  111. package/src/featureFlags/types.ts +3 -1
  112. package/src/index.ts +10 -1
  113. package/src/initializers/initializers.native.ts +29 -70
  114. package/src/initializers/workletRuntimeEntry.native.ts +3 -3
  115. package/src/memory/bundleUnpacker.native.ts +2 -4
  116. package/src/memory/serializable.native.ts +3 -3
  117. package/src/memory/synchronizableUnpacker.native.ts +6 -12
  118. package/src/platformChecker.ts +3 -2
  119. package/src/privateGlobals.d.ts +7 -2
  120. package/src/runtimeKind.ts +47 -0
  121. package/src/runtimes.native.ts +43 -2
  122. package/src/runtimes.ts +10 -0
  123. 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
+ }