react-native-nitro-fetch 1.1.0 → 1.1.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/android/src/main/java/com/margelo/nitro/nitrofetch/DevToolsReporter.kt +67 -72
- package/android/src/main/java/com/margelo/nitro/nitrofetch/DevToolsReporterImpl.kt +67 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +6 -2
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroUrlRequestBuilder.kt +4 -2
- package/ios/HybridUrlRequestBuilder.swift +19 -1
- package/ios/NitroDevToolsReporter.mm +45 -21
- package/ios/NitroFetchClient.swift +10 -0
- package/package.json +1 -1
|
@@ -1,111 +1,106 @@
|
|
|
1
1
|
package com.margelo.nitro.nitrofetch
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Thin facade over React Native's
|
|
5
|
-
* All methods are no-ops when the modern CDP debugger is not attached (guarded by
|
|
6
|
-
* `isDebuggingEnabled()` inside RN), so they are safe to call in release builds.
|
|
4
|
+
* Thin facade over React Native's `com.facebook.react.modules.network.InspectorNetworkReporter`.
|
|
7
5
|
*
|
|
8
|
-
* The
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
6
|
+
* The reporter is `internal` in RN and may be missing entirely on older versions or stripped
|
|
7
|
+
* distributions. To stay both safe (no [NoClassDefFoundError]) and fast (no per-call reflection
|
|
8
|
+
* once the class is known to exist), we use the **isolation-class pattern**:
|
|
9
|
+
*
|
|
10
|
+
* - This facade has *no* compile-time reference to `InspectorNetworkReporter`. It can always
|
|
11
|
+
* be loaded and verified by ART, even when the reporter is absent.
|
|
12
|
+
* - All direct calls live in [DevToolsReporterImpl], which is loaded reflectively *once* via
|
|
13
|
+
* `Class.forName`. If verification fails (missing class), we catch and stay in no-op mode.
|
|
14
|
+
* - On success, we hold an [Impl] interface reference and dispatch through it on every call —
|
|
15
|
+
* a single null check + a virtual call (likely devirtualized by the JIT). No reflection,
|
|
16
|
+
* no boxing, no method-handle lookups on the hot path.
|
|
14
17
|
*/
|
|
15
|
-
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
|
16
18
|
internal object DevToolsReporter {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
|
|
20
|
+
/** Stable interface in *our* package — Impl implements it; no foreign types here. */
|
|
21
|
+
internal interface Impl {
|
|
22
|
+
fun isDebuggingEnabled(): Boolean
|
|
23
|
+
fun reportRequestStart(
|
|
24
|
+
requestId: String, url: String, method: String,
|
|
25
|
+
headers: Map<String, String>, body: String, encodedDataLength: Long
|
|
26
|
+
)
|
|
27
|
+
fun reportResponseStart(
|
|
28
|
+
requestId: String, url: String, statusCode: Int,
|
|
29
|
+
headers: Map<String, String>, expectedDataLength: Long
|
|
30
|
+
)
|
|
31
|
+
fun reportDataReceived(requestId: String, length: Int)
|
|
32
|
+
fun reportResponseEnd(requestId: String, encodedDataLength: Long)
|
|
33
|
+
fun reportRequestFailed(requestId: String, cancelled: Boolean)
|
|
34
|
+
fun storeResponseBody(requestId: String, body: String, base64Encoded: Boolean)
|
|
35
|
+
fun storeResponseBodyIncremental(requestId: String, data: String)
|
|
22
36
|
}
|
|
23
37
|
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
@Volatile private var impl: Impl? = null
|
|
39
|
+
// We deliberately do NOT latch failure: cold-start prefetch can run before
|
|
40
|
+
// RN classes are realized, and we want a later request to recover. The
|
|
41
|
+
// probe is cheap (Class.forName has its own internal cache).
|
|
42
|
+
private fun resolve(): Impl? {
|
|
43
|
+
val cached = impl
|
|
44
|
+
if (cached != null) return cached
|
|
45
|
+
|
|
46
|
+
if (!isSoLoaderInitialized()) return null
|
|
26
47
|
return try {
|
|
27
|
-
com.
|
|
48
|
+
val cls = Class.forName("com.margelo.nitro.nitrofetch.DevToolsReporterImpl")
|
|
49
|
+
|
|
50
|
+
val created = cls.getDeclaredConstructor().newInstance() as Impl
|
|
51
|
+
impl = created
|
|
52
|
+
created
|
|
28
53
|
} catch (_: Throwable) {
|
|
29
|
-
|
|
54
|
+
null
|
|
30
55
|
}
|
|
31
56
|
}
|
|
32
57
|
|
|
58
|
+
private fun isSoLoaderInitialized(): Boolean = try {
|
|
59
|
+
com.facebook.soloader.SoLoader.isInitialized()
|
|
60
|
+
} catch (_: Throwable) {
|
|
61
|
+
false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// --- Hot path: one null check + interface call. JIT will devirtualize. ---
|
|
65
|
+
|
|
66
|
+
fun isDebuggingEnabled(): Boolean = resolve()?.isDebuggingEnabled() ?: false
|
|
67
|
+
|
|
33
68
|
fun reportRequestStart(
|
|
34
|
-
requestId: String,
|
|
35
|
-
|
|
36
|
-
method: String,
|
|
37
|
-
headers: Map<String, String>,
|
|
38
|
-
body: String,
|
|
39
|
-
encodedDataLength: Long
|
|
69
|
+
requestId: String, url: String, method: String,
|
|
70
|
+
headers: Map<String, String>, body: String, encodedDataLength: Long
|
|
40
71
|
) {
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
com.facebook.react.modules.network.InspectorNetworkReporter.reportRequestStart(
|
|
44
|
-
requestId, url, method, headers, body, encodedDataLength
|
|
45
|
-
)
|
|
46
|
-
com.facebook.react.modules.network.InspectorNetworkReporter.reportConnectionTiming(requestId, headers)
|
|
47
|
-
} catch (_: Throwable) {
|
|
48
|
-
}
|
|
72
|
+
impl?.reportRequestStart(requestId, url, method, headers, body, encodedDataLength)
|
|
49
73
|
}
|
|
50
74
|
|
|
51
75
|
fun reportResponseStart(
|
|
52
|
-
requestId: String,
|
|
53
|
-
|
|
54
|
-
statusCode: Int,
|
|
55
|
-
headers: Map<String, String>,
|
|
56
|
-
expectedDataLength: Long
|
|
76
|
+
requestId: String, url: String, statusCode: Int,
|
|
77
|
+
headers: Map<String, String>, expectedDataLength: Long
|
|
57
78
|
) {
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
com.facebook.react.modules.network.InspectorNetworkReporter.reportResponseStart(
|
|
61
|
-
requestId, url, statusCode, headers, expectedDataLength
|
|
62
|
-
)
|
|
63
|
-
} catch (_: Throwable) {
|
|
64
|
-
}
|
|
79
|
+
impl?.reportResponseStart(requestId, url, statusCode, headers, expectedDataLength)
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
fun reportDataReceived(requestId: String, length: Int) {
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
com.facebook.react.modules.network.InspectorNetworkReporter.reportDataReceivedImpl(requestId, length)
|
|
71
|
-
} catch (_: Throwable) {
|
|
72
|
-
}
|
|
83
|
+
impl?.reportDataReceived(requestId, length)
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
fun reportResponseEnd(requestId: String, encodedDataLength: Long) {
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
com.facebook.react.modules.network.InspectorNetworkReporter.reportResponseEnd(requestId, encodedDataLength)
|
|
79
|
-
} catch (_: Throwable) {
|
|
80
|
-
}
|
|
87
|
+
impl?.reportResponseEnd(requestId, encodedDataLength)
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
fun reportRequestFailed(requestId: String, cancelled: Boolean) {
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
com.facebook.react.modules.network.InspectorNetworkReporter.reportRequestFailed(requestId, cancelled)
|
|
87
|
-
} catch (_: Throwable) {
|
|
88
|
-
}
|
|
91
|
+
impl?.reportRequestFailed(requestId, cancelled)
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
fun storeResponseBody(requestId: String, body: String, base64Encoded: Boolean) {
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
com.facebook.react.modules.network.InspectorNetworkReporter.maybeStoreResponseBody(
|
|
95
|
-
requestId, body, base64Encoded
|
|
96
|
-
)
|
|
97
|
-
} catch (_: Throwable) {
|
|
98
|
-
}
|
|
95
|
+
impl?.storeResponseBody(requestId, body, base64Encoded)
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
fun storeResponseBodyIncremental(requestId: String, data: String) {
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
com.facebook.react.modules.network.InspectorNetworkReporter.maybeStoreResponseBodyIncremental(requestId, data)
|
|
105
|
-
} catch (_: Throwable) {
|
|
106
|
-
}
|
|
99
|
+
impl?.storeResponseBodyIncremental(requestId, data)
|
|
107
100
|
}
|
|
108
101
|
|
|
102
|
+
// --- Pure helpers, no reporter dependency. ---
|
|
103
|
+
|
|
109
104
|
fun isTextualContentType(contentType: String?): Boolean {
|
|
110
105
|
if (contentType == null) return false
|
|
111
106
|
val ct = contentType.lowercase()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrofetch
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Direct-dispatch backend for [DevToolsReporter]. Loaded reflectively from the facade
|
|
5
|
+
* via `Class.forName`, so a missing `InspectorNetworkReporter` only prevents *this* class
|
|
6
|
+
* from loading — the facade itself stays intact and degrades to a no-op.
|
|
7
|
+
*
|
|
8
|
+
* Once loaded, every call here is a plain JVM static invoke. No reflection, no boxing,
|
|
9
|
+
* no method-handle lookups. The `@Suppress` annotations bypass RN's `internal` visibility
|
|
10
|
+
* (RN has no public surface here) — this is the documented integration point.
|
|
11
|
+
*/
|
|
12
|
+
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
|
13
|
+
internal class DevToolsReporterImpl : DevToolsReporter.Impl {
|
|
14
|
+
override fun isDebuggingEnabled(): Boolean =
|
|
15
|
+
com.facebook.react.modules.network.InspectorNetworkReporter.isDebuggingEnabled()
|
|
16
|
+
|
|
17
|
+
override fun reportRequestStart(
|
|
18
|
+
requestId: String, url: String, method: String,
|
|
19
|
+
headers: Map<String, String>, body: String, encodedDataLength: Long
|
|
20
|
+
) {
|
|
21
|
+
com.facebook.react.modules.network.InspectorNetworkReporter.reportRequestStart(
|
|
22
|
+
requestId, url, method, headers, body, encodedDataLength
|
|
23
|
+
)
|
|
24
|
+
com.facebook.react.modules.network.InspectorNetworkReporter.reportConnectionTiming(
|
|
25
|
+
requestId, headers
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override fun reportResponseStart(
|
|
30
|
+
requestId: String, url: String, statusCode: Int,
|
|
31
|
+
headers: Map<String, String>, expectedDataLength: Long
|
|
32
|
+
) {
|
|
33
|
+
com.facebook.react.modules.network.InspectorNetworkReporter.reportResponseStart(
|
|
34
|
+
requestId, url, statusCode, headers, expectedDataLength
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override fun reportDataReceived(requestId: String, length: Int) {
|
|
39
|
+
com.facebook.react.modules.network.InspectorNetworkReporter.reportDataReceivedImpl(
|
|
40
|
+
requestId, length
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun reportResponseEnd(requestId: String, encodedDataLength: Long) {
|
|
45
|
+
com.facebook.react.modules.network.InspectorNetworkReporter.reportResponseEnd(
|
|
46
|
+
requestId, encodedDataLength
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override fun reportRequestFailed(requestId: String, cancelled: Boolean) {
|
|
51
|
+
com.facebook.react.modules.network.InspectorNetworkReporter.reportRequestFailed(
|
|
52
|
+
requestId, cancelled
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun storeResponseBody(requestId: String, body: String, base64Encoded: Boolean) {
|
|
57
|
+
com.facebook.react.modules.network.InspectorNetworkReporter.maybeStoreResponseBody(
|
|
58
|
+
requestId, body, base64Encoded
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override fun storeResponseBodyIncremental(requestId: String, data: String) {
|
|
63
|
+
com.facebook.react.modules.network.InspectorNetworkReporter.maybeStoreResponseBodyIncremental(
|
|
64
|
+
requestId, data
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -80,8 +80,12 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E
|
|
|
80
80
|
if (BuildConfig.NITRO_FETCH_TRACING) {
|
|
81
81
|
Trace.beginAsyncSection(traceLabel, traceCookie)
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// BuildConfig.DEBUG short-circuits in release: R8 constant-folds the
|
|
84
|
+
// && so every `if (devToolsEnabled)` block below becomes dead code and
|
|
85
|
+
// the DevToolsReporter classes drop out of the release APK entirely.
|
|
86
|
+
// The UUID generation is gated too so SecureRandom isn't touched in release.
|
|
87
|
+
val devToolsEnabled = BuildConfig.DEBUG && DevToolsReporter.isDebuggingEnabled()
|
|
88
|
+
val devToolsRequestId = if (devToolsEnabled) (req.requestId ?: UUID.randomUUID().toString()) else ""
|
|
85
89
|
val callback = object : UrlRequest.Callback() {
|
|
86
90
|
private val buffer = ByteBuffer.allocateDirect(16 * 1024)
|
|
87
91
|
private val out = java.io.ByteArrayOutputStream()
|
|
@@ -27,8 +27,10 @@ class NitroUrlRequestBuilder(
|
|
|
27
27
|
|
|
28
28
|
private val builder: CronetUrlRequest.Builder
|
|
29
29
|
private val byteBuffer: ByteBuffer
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// BuildConfig.DEBUG short-circuits in release so R8 strips DevTools paths.
|
|
31
|
+
// UUID generation is gated too so SecureRandom isn't touched in release.
|
|
32
|
+
private val devToolsEnabled: Boolean = BuildConfig.DEBUG && DevToolsReporter.isDebuggingEnabled()
|
|
33
|
+
private val devToolsRequestId: String = if (devToolsEnabled) UUID.randomUUID().toString() else ""
|
|
32
34
|
private var devToolsBytes: Int = 0
|
|
33
35
|
private var devToolsTextual: Boolean = false
|
|
34
36
|
private var httpMethod: String = "GET"
|
|
@@ -16,7 +16,13 @@ class HybridUrlRequestBuilder: HybridUrlRequestBuilderSpec {
|
|
|
16
16
|
|
|
17
17
|
private var urlRequest: URLRequest
|
|
18
18
|
private var priority: Float = 0.5
|
|
19
|
-
private let devToolsRequestId: String =
|
|
19
|
+
private let devToolsRequestId: String = {
|
|
20
|
+
#if DEBUG
|
|
21
|
+
return UUID().uuidString
|
|
22
|
+
#else
|
|
23
|
+
return ""
|
|
24
|
+
#endif
|
|
25
|
+
}()
|
|
20
26
|
|
|
21
27
|
init(
|
|
22
28
|
url: String,
|
|
@@ -109,9 +115,11 @@ class HybridUrlRequestBuilder: HybridUrlRequestBuilderSpec {
|
|
|
109
115
|
task.priority = priority
|
|
110
116
|
delegate.task = task
|
|
111
117
|
|
|
118
|
+
#if DEBUG
|
|
112
119
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
113
120
|
NitroDevToolsReporter.reportRequestStart(withRequest: devToolsRequestId, request: urlRequest)
|
|
114
121
|
}
|
|
122
|
+
#endif
|
|
115
123
|
|
|
116
124
|
let request = HybridUrlRequest(task: task, delegate: delegate)
|
|
117
125
|
delegate.hybridRequest = request
|
|
@@ -192,17 +200,21 @@ private class URLSessionDelegateAdapter: NSObject, URLSessionDataDelegate, URLSe
|
|
|
192
200
|
if let error = error {
|
|
193
201
|
let nsError = error as NSError
|
|
194
202
|
if nsError.code == NSURLErrorCancelled {
|
|
203
|
+
#if DEBUG
|
|
195
204
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
196
205
|
NitroDevToolsReporter.reportRequestFailed(self.devToolsRequestId, cancelled: true)
|
|
197
206
|
}
|
|
207
|
+
#endif
|
|
198
208
|
if let callback = self.onCanceled {
|
|
199
209
|
let nitroInfo = self.response?.toNitro()
|
|
200
210
|
callback(nitroInfo)
|
|
201
211
|
}
|
|
202
212
|
} else {
|
|
213
|
+
#if DEBUG
|
|
203
214
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
204
215
|
NitroDevToolsReporter.reportRequestFailed(self.devToolsRequestId, cancelled: false)
|
|
205
216
|
}
|
|
217
|
+
#endif
|
|
206
218
|
if let callback = self.onFailed {
|
|
207
219
|
let nitroError = error.toNitro()
|
|
208
220
|
let nitroInfo = self.response?.toNitro()
|
|
@@ -210,9 +222,11 @@ private class URLSessionDelegateAdapter: NSObject, URLSessionDataDelegate, URLSe
|
|
|
210
222
|
}
|
|
211
223
|
}
|
|
212
224
|
} else if let response = self.response {
|
|
225
|
+
#if DEBUG
|
|
213
226
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
214
227
|
NitroDevToolsReporter.reportResponseEnd(self.devToolsRequestId, encodedDataLength: self.devToolsBytes)
|
|
215
228
|
}
|
|
229
|
+
#endif
|
|
216
230
|
if let callback = self.onSucceeded {
|
|
217
231
|
let info = response.toNitro()
|
|
218
232
|
callback(info)
|
|
@@ -236,6 +250,7 @@ private class URLSessionDelegateAdapter: NSObject, URLSessionDataDelegate, URLSe
|
|
|
236
250
|
|
|
237
251
|
executor.sync { [weak self] in
|
|
238
252
|
guard let self = self else { return }
|
|
253
|
+
#if DEBUG
|
|
239
254
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
240
255
|
var headerDict: [String: String] = [:]
|
|
241
256
|
httpResponse.allHeaderFields.forEach { k, v in
|
|
@@ -250,6 +265,7 @@ private class URLSessionDelegateAdapter: NSObject, URLSessionDataDelegate, URLSe
|
|
|
250
265
|
let ct = headerDict["Content-Type"] ?? headerDict["content-type"]
|
|
251
266
|
self.devToolsTextual = NitroDevToolsReporter.isTextualContentType(ct)
|
|
252
267
|
}
|
|
268
|
+
#endif
|
|
253
269
|
if let callback = self.onResponseStarted {
|
|
254
270
|
let info = httpResponse.toNitro()
|
|
255
271
|
callback(info)
|
|
@@ -268,6 +284,7 @@ private class URLSessionDelegateAdapter: NSObject, URLSessionDataDelegate, URLSe
|
|
|
268
284
|
executor.sync { [weak self] in
|
|
269
285
|
guard let self = self, let response = self.response else { return }
|
|
270
286
|
|
|
287
|
+
#if DEBUG
|
|
271
288
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
272
289
|
self.devToolsBytes += data.count
|
|
273
290
|
NitroDevToolsReporter.reportDataReceived(self.devToolsRequestId, length: data.count)
|
|
@@ -275,6 +292,7 @@ private class URLSessionDelegateAdapter: NSObject, URLSessionDataDelegate, URLSe
|
|
|
275
292
|
NitroDevToolsReporter.storeResponseBodyIncremental(self.devToolsRequestId, text: text)
|
|
276
293
|
}
|
|
277
294
|
}
|
|
295
|
+
#endif
|
|
278
296
|
|
|
279
297
|
let arrayBuffer: ArrayBuffer
|
|
280
298
|
do {
|
|
@@ -10,29 +10,39 @@
|
|
|
10
10
|
#define NITRO_HAS_NETWORK_REPORTER 0
|
|
11
11
|
#endif
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
13
|
+
// During cold-start prefetch, RN's RCTInspectorNetworkReporter class may not
|
|
14
|
+
// be realized yet (its underlying C++ NetworkReporter singleton is brought up
|
|
15
|
+
// when the bridge initializes). Every entry point goes through +reporterClass,
|
|
16
|
+
// which uses NSClassFromString so a missing class becomes a clean no-op
|
|
17
|
+
// instead of crashing on a not-yet-initialized C++ singleton.
|
|
18
18
|
|
|
19
19
|
@implementation NitroDevToolsReporter
|
|
20
20
|
|
|
21
|
-
+ (
|
|
21
|
+
+ (Class _Nullable)reporterClass {
|
|
22
22
|
#if NITRO_HAS_NETWORK_REPORTER
|
|
23
|
-
|
|
23
|
+
static Class cached = Nil;
|
|
24
|
+
if (cached == Nil) {
|
|
25
|
+
cached = NSClassFromString(@"RCTInspectorNetworkReporter");
|
|
26
|
+
}
|
|
27
|
+
return cached;
|
|
24
28
|
#else
|
|
25
|
-
return
|
|
29
|
+
return Nil;
|
|
26
30
|
#endif
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
+ (BOOL)isDebuggingEnabled {
|
|
34
|
+
return [self reporterClass] != Nil;
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
+ (void)reportRequestStartWithRequest:(NSString *)requestId
|
|
30
38
|
request:(NSURLRequest *)request {
|
|
31
39
|
#if NITRO_HAS_NETWORK_REPORTER
|
|
32
40
|
if (request == nil) return;
|
|
41
|
+
Class cls = [self reporterClass];
|
|
42
|
+
if (cls == Nil) return;
|
|
33
43
|
int encoded = (int)(request.HTTPBody.length);
|
|
34
|
-
[
|
|
35
|
-
[
|
|
44
|
+
[cls reportRequestStart:requestId request:request encodedDataLength:encoded];
|
|
45
|
+
[cls reportConnectionTiming:requestId request:request];
|
|
36
46
|
#endif
|
|
37
47
|
}
|
|
38
48
|
|
|
@@ -42,6 +52,8 @@
|
|
|
42
52
|
headers:(NSDictionary<NSString *, NSString *> *)headers
|
|
43
53
|
bodyString:(NSString *)bodyString {
|
|
44
54
|
#if NITRO_HAS_NETWORK_REPORTER
|
|
55
|
+
Class cls = [self reporterClass];
|
|
56
|
+
if (cls == Nil) return;
|
|
45
57
|
NSURL *u = [NSURL URLWithString:url];
|
|
46
58
|
if (u == nil) return;
|
|
47
59
|
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:u];
|
|
@@ -53,8 +65,8 @@
|
|
|
53
65
|
req.HTTPBody = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
|
|
54
66
|
}
|
|
55
67
|
NSInteger encoded = bodyString ? (NSInteger)[bodyString lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : 0;
|
|
56
|
-
[
|
|
57
|
-
[
|
|
68
|
+
[cls reportRequestStart:requestId request:req encodedDataLength:(int)encoded];
|
|
69
|
+
[cls reportConnectionTiming:requestId request:req];
|
|
58
70
|
#endif
|
|
59
71
|
}
|
|
60
72
|
|
|
@@ -63,40 +75,48 @@
|
|
|
63
75
|
statusCode:(NSInteger)statusCode
|
|
64
76
|
headers:(NSDictionary<NSString *, NSString *> *)headers {
|
|
65
77
|
#if NITRO_HAS_NETWORK_REPORTER
|
|
78
|
+
Class cls = [self reporterClass];
|
|
79
|
+
if (cls == Nil) return;
|
|
66
80
|
NSURL *u = [NSURL URLWithString:url];
|
|
67
81
|
if (u == nil) return;
|
|
68
82
|
NSHTTPURLResponse *resp = [[NSHTTPURLResponse alloc] initWithURL:u
|
|
69
83
|
statusCode:statusCode
|
|
70
84
|
HTTPVersion:@"HTTP/1.1"
|
|
71
85
|
headerFields:headers];
|
|
72
|
-
[
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
[cls reportResponseStart:requestId
|
|
87
|
+
response:resp
|
|
88
|
+
statusCode:(int)statusCode
|
|
89
|
+
headers:headers];
|
|
76
90
|
#endif
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
+ (void)reportDataReceived:(NSString *)requestId length:(NSInteger)length {
|
|
80
94
|
#if NITRO_HAS_NETWORK_REPORTER
|
|
81
95
|
if (length <= 0) return;
|
|
96
|
+
Class cls = [self reporterClass];
|
|
97
|
+
if (cls == Nil) return;
|
|
82
98
|
// Only data.length is read by the underlying reporter. Avoid allocating a
|
|
83
99
|
// zero-filled buffer by handing NSData a non-owned byte pointer and the
|
|
84
100
|
// intended length.
|
|
85
101
|
static uint8_t sentinel;
|
|
86
102
|
NSData *sized = [NSData dataWithBytesNoCopy:&sentinel length:(NSUInteger)length freeWhenDone:NO];
|
|
87
|
-
[
|
|
103
|
+
[cls reportDataReceived:requestId data:sized];
|
|
88
104
|
#endif
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
+ (void)reportResponseEnd:(NSString *)requestId encodedDataLength:(NSInteger)length {
|
|
92
108
|
#if NITRO_HAS_NETWORK_REPORTER
|
|
93
|
-
[
|
|
109
|
+
Class cls = [self reporterClass];
|
|
110
|
+
if (cls == Nil) return;
|
|
111
|
+
[cls reportResponseEnd:requestId encodedDataLength:(int)length];
|
|
94
112
|
#endif
|
|
95
113
|
}
|
|
96
114
|
|
|
97
115
|
+ (void)reportRequestFailed:(NSString *)requestId cancelled:(BOOL)cancelled {
|
|
98
116
|
#if NITRO_HAS_NETWORK_REPORTER
|
|
99
|
-
[
|
|
117
|
+
Class cls = [self reporterClass];
|
|
118
|
+
if (cls == Nil) return;
|
|
119
|
+
[cls reportRequestFailed:requestId cancelled:cancelled];
|
|
100
120
|
#endif
|
|
101
121
|
}
|
|
102
122
|
|
|
@@ -104,13 +124,17 @@
|
|
|
104
124
|
data:(NSData *)data
|
|
105
125
|
base64Encoded:(BOOL)base64Encoded {
|
|
106
126
|
#if NITRO_HAS_NETWORK_REPORTER
|
|
107
|
-
|
|
127
|
+
Class cls = [self reporterClass];
|
|
128
|
+
if (cls == Nil) return;
|
|
129
|
+
[cls maybeStoreResponseBody:requestId data:data base64Encoded:base64Encoded];
|
|
108
130
|
#endif
|
|
109
131
|
}
|
|
110
132
|
|
|
111
133
|
+ (void)storeResponseBodyIncremental:(NSString *)requestId text:(NSString *)text {
|
|
112
134
|
#if NITRO_HAS_NETWORK_REPORTER
|
|
113
|
-
[
|
|
135
|
+
Class cls = [self reporterClass];
|
|
136
|
+
if (cls == Nil) return;
|
|
137
|
+
[cls maybeStoreResponseBodyIncremental:requestId data:text];
|
|
114
138
|
#endif
|
|
115
139
|
}
|
|
116
140
|
|
|
@@ -170,26 +170,34 @@ final class NitroFetchClient: HybridNitroFetchClientSpec {
|
|
|
170
170
|
"%{public}s %{public}s", traceMethod, tracePath)
|
|
171
171
|
#endif
|
|
172
172
|
|
|
173
|
+
// DevTools/CDP reporting is gated on `#if DEBUG` so the entire block is
|
|
174
|
+
// compiled out of release builds — no runtime cost, no symbol references.
|
|
175
|
+
#if DEBUG
|
|
173
176
|
let devToolsId = req.requestId ?? UUID().uuidString
|
|
174
177
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
175
178
|
NitroDevToolsReporter.reportRequestStart(withRequest: devToolsId, request: urlRequest)
|
|
176
179
|
}
|
|
180
|
+
#endif
|
|
177
181
|
|
|
178
182
|
let data: Data
|
|
179
183
|
let response: URLResponse
|
|
180
184
|
do {
|
|
181
185
|
(data, response) = try await session.data(for: urlRequest, delegate: delegate)
|
|
182
186
|
} catch {
|
|
187
|
+
#if DEBUG
|
|
183
188
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
184
189
|
let cancelled = (error as NSError).code == NSURLErrorCancelled
|
|
185
190
|
NitroDevToolsReporter.reportRequestFailed(devToolsId, cancelled: cancelled)
|
|
186
191
|
}
|
|
192
|
+
#endif
|
|
187
193
|
throw error
|
|
188
194
|
}
|
|
189
195
|
guard let http = response as? HTTPURLResponse else {
|
|
196
|
+
#if DEBUG
|
|
190
197
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
191
198
|
NitroDevToolsReporter.reportRequestFailed(devToolsId, cancelled: false)
|
|
192
199
|
}
|
|
200
|
+
#endif
|
|
193
201
|
throw NSError(domain: "NitroFetch", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid response"])
|
|
194
202
|
}
|
|
195
203
|
|
|
@@ -198,6 +206,7 @@ final class NitroFetchClient: HybridNitroFetchClientSpec {
|
|
|
198
206
|
return NitroHeader(key: key, value: String(describing: v))
|
|
199
207
|
}
|
|
200
208
|
|
|
209
|
+
#if DEBUG
|
|
201
210
|
if NitroDevToolsReporter.isDebuggingEnabled() {
|
|
202
211
|
var headerDict: [String: String] = [:]
|
|
203
212
|
for h in headersPairs { headerDict[h.key] = h.value }
|
|
@@ -217,6 +226,7 @@ final class NitroFetchClient: HybridNitroFetchClientSpec {
|
|
|
217
226
|
}
|
|
218
227
|
NitroDevToolsReporter.reportResponseEnd(devToolsId, encodedDataLength: data.count)
|
|
219
228
|
}
|
|
229
|
+
#endif
|
|
220
230
|
|
|
221
231
|
// Choose bodyString by default (matching Android’s first pass)
|
|
222
232
|
let charset = NitroFetchClient.detectCharset(from: http) ?? String.Encoding.utf8
|