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.
@@ -1,111 +1,106 @@
1
1
  package com.margelo.nitro.nitrofetch
2
2
 
3
3
  /**
4
- * Thin facade over React Native's [com.facebook.react.modules.network.InspectorNetworkReporter].
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 underlying reporter is marked `internal` in RN but is the officially documented
9
- * Kotlin entry point for third-party networking libraries to surface requests in the
10
- * Fusebox / RN DevTools Network tab. We intentionally bypass the visibility modifier
11
- * rather than duplicating the JNI bindings, matching the pattern used by other
12
- * community HTTP clients. If the class isn't present (RN < 0.76 or a trimmed
13
- * distribution), every call becomes a no-op.
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
- private val available: Boolean = try {
18
- Class.forName("com.facebook.react.modules.network.InspectorNetworkReporter")
19
- true
20
- } catch (_: Throwable) {
21
- false
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
- fun isDebuggingEnabled(): Boolean {
25
- if (!available) return false
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.facebook.react.modules.network.InspectorNetworkReporter.isDebuggingEnabled()
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
- false
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
- url: String,
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
- if (!available) return
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
- url: String,
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
- if (!available) return
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
- if (!available) return
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
- if (!available) return
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
- if (!available) return
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
- if (!available) return
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
- if (!available) return
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
- val devToolsRequestId = req.requestId ?: UUID.randomUUID().toString()
84
- val devToolsEnabled = DevToolsReporter.isDebuggingEnabled()
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
- private val devToolsRequestId: String = UUID.randomUUID().toString()
31
- private val devToolsEnabled: Boolean = DevToolsReporter.isDebuggingEnabled()
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 = UUID().uuidString
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
- // The Objective-C wrapper does not expose -isDebuggingEnabled publicly,
14
- // but the underlying C++ NetworkReporter does. We avoid depending on the
15
- // C++ header and instead rely on RN guarding every report call internally.
16
- // For our own body-capture short-circuit we assume enabled when the class
17
- // exists; the underlying calls are still no-ops when no debugger attached.
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
- + (BOOL)isDebuggingEnabled {
21
+ + (Class _Nullable)reporterClass {
22
22
  #if NITRO_HAS_NETWORK_REPORTER
23
- return YES;
23
+ static Class cached = Nil;
24
+ if (cached == Nil) {
25
+ cached = NSClassFromString(@"RCTInspectorNetworkReporter");
26
+ }
27
+ return cached;
24
28
  #else
25
- return NO;
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
- [RCTInspectorNetworkReporter reportRequestStart:requestId request:request encodedDataLength:encoded];
35
- [RCTInspectorNetworkReporter reportConnectionTiming:requestId request:request];
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
- [RCTInspectorNetworkReporter reportRequestStart:requestId request:req encodedDataLength:(int)encoded];
57
- [RCTInspectorNetworkReporter reportConnectionTiming:requestId request:req];
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
- [RCTInspectorNetworkReporter reportResponseStart:requestId
73
- response:resp
74
- statusCode:(int)statusCode
75
- headers:headers];
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
- [RCTInspectorNetworkReporter reportDataReceived:requestId data:sized];
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
- [RCTInspectorNetworkReporter reportResponseEnd:requestId encodedDataLength:(int)length];
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
- [RCTInspectorNetworkReporter reportRequestFailed:requestId cancelled:cancelled];
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
- [RCTInspectorNetworkReporter maybeStoreResponseBody:requestId data:data base64Encoded:base64Encoded];
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
- [RCTInspectorNetworkReporter maybeStoreResponseBodyIncremental:requestId data:text];
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-fetch",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Awesome Fetch :)",
5
5
  "main": "./lib/module/index.js",
6
6
  "module": "./lib/module/index.js",