react-native 0.83.2 → 0.83.4

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 (32) hide show
  1. package/Libraries/Core/ReactNativeVersion.js +1 -1
  2. package/Libraries/Core/setUpReactDevTools.js +23 -6
  3. package/Libraries/Network/RCTHTTPRequestHandler.h +9 -0
  4. package/Libraries/Network/RCTHTTPRequestHandler.mm +15 -1
  5. package/Libraries/WebSocket/RCTReconnectingWebSocket.m +4 -1
  6. package/React/Base/RCTBundleURLProvider.mm +5 -3
  7. package/React/Base/RCTDevSupportHttpHeaders.h +24 -0
  8. package/React/Base/RCTDevSupportHttpHeaders.m +65 -0
  9. package/React/Base/RCTMultipartDataTask.h +9 -0
  10. package/React/Base/RCTMultipartDataTask.m +16 -1
  11. package/React/Base/RCTVersion.m +1 -1
  12. package/React/CoreModules/RCTWebSocketModule.h +6 -0
  13. package/React/CoreModules/RCTWebSocketModule.mm +14 -1
  14. package/React/DevSupport/RCTInspectorDevServerHelper.mm +33 -22
  15. package/React/DevSupport/RCTInspectorNetworkHelper.mm +2 -0
  16. package/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm +5 -1
  17. package/ReactAndroid/api/ReactAndroid.api +1 -0
  18. package/ReactAndroid/gradle.properties +1 -1
  19. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.kt +2 -8
  20. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.kt +17 -13
  21. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/PackagerStatusCheck.kt +10 -19
  22. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxContentView.kt +2 -2
  23. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/DevSupportHttpClient.kt +49 -0
  24. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/InspectorNetworkHelper.kt +1 -12
  25. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  26. package/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt +3 -4
  27. package/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/JSPackagerClient.kt +2 -1
  28. package/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/ReconnectingWebSocket.kt +2 -8
  29. package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
  30. package/package.json +8 -8
  31. package/scripts/replace-rncore-version.js +62 -8
  32. package/settings.gradle.kts +21 -1
@@ -28,7 +28,7 @@
28
28
  export default class ReactNativeVersion {
29
29
  static major: number = 0;
30
30
  static minor: number = 83;
31
- static patch: number = 2;
31
+ static patch: number = 4;
32
32
  static prerelease: string | null = null;
33
33
 
34
34
  static getVersionString(): string {
@@ -146,17 +146,34 @@ if (__DEV__) {
146
146
  ? guessHostFromDevServerUrl(devServer.url)
147
147
  : 'localhost';
148
148
 
149
- // Read the optional global variable for backward compatibility.
150
- // It was added in https://github.com/facebook/react-native/commit/bf2b435322e89d0aeee8792b1c6e04656c2719a0.
151
- const port =
149
+ // Derive scheme and port from the dev server URL when possible,
150
+ // falling back to ws://host:8097 for local development.
151
+ let wsScheme = 'ws';
152
+ let port = 8097;
153
+
154
+ if (
152
155
  // $FlowFixMe[prop-missing]
153
156
  // $FlowFixMe[incompatible-use]
154
157
  window.__REACT_DEVTOOLS_PORT__ != null
155
- ? window.__REACT_DEVTOOLS_PORT__
156
- : 8097;
158
+ ) {
159
+ // $FlowFixMe[prop-missing]
160
+ port = window.__REACT_DEVTOOLS_PORT__;
161
+ } else if (devServer.bundleLoadedFromServer) {
162
+ try {
163
+ const devUrl = new URL(devServer.url);
164
+ if (devUrl.protocol === 'https:') {
165
+ wsScheme = 'wss';
166
+ }
167
+ if (devUrl.port) {
168
+ port = parseInt(devUrl.port, 10);
169
+ } else if (devUrl.protocol === 'https:') {
170
+ port = 443;
171
+ }
172
+ } catch (e) {}
173
+ }
157
174
 
158
175
  const WebSocket = require('../WebSocket/WebSocket').default;
159
- ws = new WebSocket('ws://' + host + ':' + port);
176
+ ws = new WebSocket(wsScheme + '://' + host + ':' + port);
160
177
  ws.addEventListener('close', event => {
161
178
  isWebSocketOpen = false;
162
179
  });
@@ -14,6 +14,15 @@ typedef NSURLSessionConfiguration * (^NSURLSessionConfigurationProvider)(void);
14
14
  * app.
15
15
  */
16
16
  RCT_EXTERN void RCTSetCustomNSURLSessionConfigurationProvider(NSURLSessionConfigurationProvider /*provider*/);
17
+
18
+ typedef NSURLRequest *_Nullable (^RCTHTTPRequestInterceptor)(NSURLRequest *request);
19
+ /**
20
+ * The block provided via this function can inspect/modify HTTP requests before
21
+ * they are sent. Return a modified request to override, or nil to use the
22
+ * original request unchanged.
23
+ */
24
+ RCT_EXTERN void RCTSetCustomHTTPRequestInterceptor(RCTHTTPRequestInterceptor /*interceptor*/);
25
+
17
26
  /**
18
27
  * This is the default RCTURLRequestHandler implementation for HTTP requests.
19
28
  */
@@ -25,6 +25,13 @@ void RCTSetCustomNSURLSessionConfigurationProvider(NSURLSessionConfigurationProv
25
25
  urlSessionConfigurationProvider = provider;
26
26
  }
27
27
 
28
+ static RCTHTTPRequestInterceptor httpRequestInterceptor;
29
+
30
+ void RCTSetCustomHTTPRequestInterceptor(RCTHTTPRequestInterceptor interceptor)
31
+ {
32
+ httpRequestInterceptor = interceptor;
33
+ }
34
+
28
35
  @implementation RCTHTTPRequestHandler {
29
36
  NSMapTable *_delegates;
30
37
  NSURLSession *_session;
@@ -99,7 +106,14 @@ RCT_EXPORT_MODULE()
99
106
  valueOptions:NSPointerFunctionsStrongMemory
100
107
  capacity:0];
101
108
  }
102
- NSURLSessionDataTask *task = [_session dataTaskWithRequest:request];
109
+ NSURLRequest *finalRequest = request;
110
+ if (httpRequestInterceptor != nullptr) {
111
+ NSURLRequest *intercepted = httpRequestInterceptor(request);
112
+ if (intercepted != nil) {
113
+ finalRequest = intercepted;
114
+ }
115
+ }
116
+ NSURLSessionDataTask *task = [_session dataTaskWithRequest:finalRequest];
103
117
  [_delegates setObject:delegate forKey:task];
104
118
  [task resume];
105
119
  return task;
@@ -9,6 +9,7 @@
9
9
 
10
10
  #import <React/RCTConvert.h>
11
11
  #import <React/RCTDefines.h>
12
+ #import <React/RCTDevSupportHttpHeaders.h>
12
13
 
13
14
  #import <SocketRocket/SRWebSocket.h>
14
15
 
@@ -46,7 +47,9 @@
46
47
  {
47
48
  [self stop];
48
49
  _stopped = NO;
49
- _socket = [[SRWebSocket alloc] initWithURL:_url];
50
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url];
51
+ [[RCTDevSupportHttpHeaders sharedInstance] applyHeadersToRequest:request];
52
+ _socket = [[SRWebSocket alloc] initWithURLRequest:request];
50
53
  _socket.delegate = self;
51
54
  [_socket setDelegateDispatchQueue:_delegateDispatchQueue];
52
55
  [_socket open];
@@ -10,6 +10,7 @@
10
10
  #import "RCTConstants.h"
11
11
  #import "RCTConvert.h"
12
12
  #import "RCTDefines.h"
13
+ #import "RCTDevSupportHttpHeaders.h"
13
14
  #import "RCTLog.h"
14
15
 
15
16
  #import <jsinspector-modern/InspectorFlags.h>
@@ -93,9 +94,10 @@ static NSURL *serverRootWithHostPort(NSString *hostPort, NSString *scheme)
93
94
  NSURL *url = [serverRootWithHostPort(hostPort, scheme) URLByAppendingPathComponent:@"status"];
94
95
 
95
96
  NSURLSession *session = [NSURLSession sharedSession];
96
- NSURLRequest *request = [NSURLRequest requestWithURL:url
97
- cachePolicy:NSURLRequestUseProtocolCachePolicy
98
- timeoutInterval:10];
97
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
98
+ cachePolicy:NSURLRequestUseProtocolCachePolicy
99
+ timeoutInterval:10];
100
+ [[RCTDevSupportHttpHeaders sharedInstance] applyHeadersToRequest:request];
99
101
  __block NSURLResponse *response;
100
102
  __block NSData *data;
101
103
 
@@ -0,0 +1,24 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ #import <Foundation/Foundation.h>
9
+
10
+ /**
11
+ * Thread-safe singleton that holds custom HTTP headers to be applied
12
+ * to all devsupport network requests (bundle fetches, packager status
13
+ * checks, inspector and HMR WebSocket connections).
14
+ */
15
+ @interface RCTDevSupportHttpHeaders : NSObject
16
+
17
+ + (instancetype)sharedInstance;
18
+
19
+ - (void)addRequestHeader:(NSString *)name value:(NSString *)value;
20
+ - (void)removeRequestHeader:(NSString *)name;
21
+ - (NSDictionary<NSString *, NSString *> *)allHeaders;
22
+ - (void)applyHeadersToRequest:(NSMutableURLRequest *)request;
23
+
24
+ @end
@@ -0,0 +1,65 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ #import "RCTDevSupportHttpHeaders.h"
9
+
10
+ @implementation RCTDevSupportHttpHeaders {
11
+ NSMutableDictionary<NSString *, NSString *> *_headers;
12
+ dispatch_queue_t _queue;
13
+ }
14
+
15
+ + (instancetype)sharedInstance
16
+ {
17
+ static RCTDevSupportHttpHeaders *sharedInstance;
18
+ static dispatch_once_t onceToken;
19
+ dispatch_once(&onceToken, ^{
20
+ sharedInstance = [[RCTDevSupportHttpHeaders alloc] init];
21
+ });
22
+ return sharedInstance;
23
+ }
24
+
25
+ - (instancetype)init
26
+ {
27
+ if (self = [super init]) {
28
+ _headers = [NSMutableDictionary new];
29
+ _queue = dispatch_queue_create("com.facebook.react.RCTDevSupportHttpHeaders", DISPATCH_QUEUE_SERIAL);
30
+ }
31
+ return self;
32
+ }
33
+
34
+ - (void)addRequestHeader:(NSString *)name value:(NSString *)value
35
+ {
36
+ dispatch_sync(_queue, ^{
37
+ self->_headers[name] = value;
38
+ });
39
+ }
40
+
41
+ - (void)removeRequestHeader:(NSString *)name
42
+ {
43
+ dispatch_sync(_queue, ^{
44
+ [self->_headers removeObjectForKey:name];
45
+ });
46
+ }
47
+
48
+ - (NSDictionary<NSString *, NSString *> *)allHeaders
49
+ {
50
+ __block NSDictionary<NSString *, NSString *> *snapshot;
51
+ dispatch_sync(_queue, ^{
52
+ snapshot = [self->_headers copy];
53
+ });
54
+ return snapshot;
55
+ }
56
+
57
+ - (void)applyHeadersToRequest:(NSMutableURLRequest *)request
58
+ {
59
+ NSDictionary<NSString *, NSString *> *headers = [self allHeaders];
60
+ [headers enumerateKeysAndObjectsUsingBlock:^(NSString *headerName, NSString *headerValue, BOOL *stop) {
61
+ [request setValue:headerValue forHTTPHeaderField:headerName];
62
+ }];
63
+ }
64
+
65
+ @end
@@ -7,6 +7,7 @@
7
7
 
8
8
  #import <Foundation/Foundation.h>
9
9
 
10
+ #import <React/RCTDefines.h>
10
11
  #import <React/RCTMultipartStreamReader.h>
11
12
 
12
13
  typedef void (^RCTMultipartDataTaskCallback)(
@@ -16,6 +17,14 @@ typedef void (^RCTMultipartDataTaskCallback)(
16
17
  NSError *error,
17
18
  BOOL done);
18
19
 
20
+ typedef NSURLRequest * _Nullable (^RCTMultipartDataTaskRequestInterceptor)(NSURLRequest *request);
21
+ /**
22
+ * The block provided via this function can inspect/modify multipart data task
23
+ * requests before they are sent. Return a modified request to override, or nil
24
+ * to use the original request unchanged.
25
+ */
26
+ RCT_EXTERN void RCTSetCustomMultipartDataTaskRequestInterceptor(RCTMultipartDataTaskRequestInterceptor /*interceptor*/);
27
+
19
28
  @interface RCTMultipartDataTask : NSObject
20
29
 
21
30
  - (instancetype)initWithURL:(NSURL *)url
@@ -7,6 +7,13 @@
7
7
 
8
8
  #import "RCTMultipartDataTask.h"
9
9
 
10
+ static RCTMultipartDataTaskRequestInterceptor multipartRequestInterceptor;
11
+
12
+ void RCTSetCustomMultipartDataTaskRequestInterceptor(RCTMultipartDataTaskRequestInterceptor interceptor)
13
+ {
14
+ multipartRequestInterceptor = interceptor;
15
+ }
16
+
10
17
  @interface RCTMultipartDataTask () <NSURLSessionDataDelegate, NSURLSessionDataDelegate>
11
18
 
12
19
  @end
@@ -40,7 +47,15 @@
40
47
  delegateQueue:nil];
41
48
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url];
42
49
  [request addValue:@"multipart/mixed" forHTTPHeaderField:@"Accept"];
43
- NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
50
+ NSURLRequest *finalRequest = request;
51
+ if (multipartRequestInterceptor != nil) {
52
+ NSURLRequest *intercepted = multipartRequestInterceptor(request);
53
+ if (intercepted != nil) {
54
+ finalRequest = intercepted;
55
+ }
56
+ }
57
+ NSLog(@"[RCTMultipartDataTask] %@ %@", finalRequest.HTTPMethod ?: @"GET", finalRequest.URL.absoluteString);
58
+ NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:finalRequest];
44
59
  [dataTask resume];
45
60
  [session finishTasksAndInvalidate];
46
61
  }
@@ -23,7 +23,7 @@ NSDictionary* RCTGetReactNativeVersion(void)
23
23
  __rnVersion = @{
24
24
  RCTVersionMajor: @(0),
25
25
  RCTVersionMinor: @(83),
26
- RCTVersionPatch: @(2),
26
+ RCTVersionPatch: @(4),
27
27
  RCTVersionPrerelease: [NSNull null],
28
28
  };
29
29
  });
@@ -18,6 +18,12 @@ NS_ASSUME_NONNULL_BEGIN
18
18
 
19
19
  @end
20
20
 
21
+ @class SRWebSocket;
22
+
23
+ typedef SRWebSocket * (^SRWebSocketProvider)(NSURLRequest *request);
24
+
25
+ RCT_EXTERN void RCTSetCustomSRWebSocketProvider(SRWebSocketProvider provider);
26
+
21
27
  @interface RCTWebSocketModule : RCTEventEmitter
22
28
 
23
29
  // Register a custom handler for a specific websocket. The handler will be strongly held by the WebSocketModule.
@@ -34,6 +34,13 @@
34
34
 
35
35
  @end
36
36
 
37
+ static SRWebSocketProvider srWebSocketProvider;
38
+
39
+ void RCTSetCustomSRWebSocketProvider(SRWebSocketProvider provider)
40
+ {
41
+ srWebSocketProvider = provider;
42
+ }
43
+
37
44
  @implementation RCTWebSocketModule {
38
45
  NSMutableDictionary<NSNumber *, SRWebSocket *> *_sockets;
39
46
  NSMutableDictionary<NSNumber *, id<RCTWebSocketContentHandler>> *_contentHandlers;
@@ -88,7 +95,13 @@ RCT_EXPORT_METHOD(
88
95
  }];
89
96
  }
90
97
 
91
- SRWebSocket *webSocket = [[SRWebSocket alloc] initWithURLRequest:request protocols:protocols];
98
+ SRWebSocket *webSocket;
99
+ if (srWebSocketProvider != nullptr) {
100
+ webSocket = srWebSocketProvider(request);
101
+ }
102
+ if (webSocket == nil) {
103
+ webSocket = [[SRWebSocket alloc] initWithURLRequest:request protocols:protocols];
104
+ }
92
105
  [webSocket setDelegateDispatchQueue:[self methodQueue]];
93
106
  webSocket.delegate = self;
94
107
  webSocket.reactTag = @(socketID);
@@ -14,33 +14,39 @@
14
14
 
15
15
  #import <React/RCTCxxInspectorPackagerConnection.h>
16
16
  #import <React/RCTDefines.h>
17
+ #import <React/RCTDevSupportHttpHeaders.h>
17
18
 
18
19
  #import <CommonCrypto/CommonCrypto.h>
19
20
  #import <jsinspector-modern/InspectorFlags.h>
20
21
 
21
22
  static NSString *const kDebuggerMsgDisable = @"{ \"id\":1,\"method\":\"Debugger.disable\" }";
23
+ static const int kDefaultMetroPort = 8081;
22
24
 
23
25
  static NSString *getServerHost(NSURL *bundleURL)
24
26
  {
25
- NSNumber *port = @8081;
26
- NSString *portStr = [[[NSProcessInfo processInfo] environment] objectForKey:@"RCT_METRO_PORT"];
27
- if ((portStr != nullptr) && [portStr length] > 0) {
28
- port = [NSNumber numberWithInt:[portStr intValue]];
29
- }
30
- if ([bundleURL port] != nullptr) {
31
- port = [bundleURL port];
32
- }
33
27
  NSString *host = [bundleURL host];
34
28
  if (host == nullptr) {
35
29
  host = @"localhost";
36
30
  }
37
31
 
38
- // this is consistent with the Android implementation, where http:// is the
39
- // hardcoded implicit scheme for the debug server. Note, packagerURL
40
- // technically looks like it could handle schemes/protocols other than HTTP,
41
- // so rather than force HTTP, leave it be for now, in case someone is relying
42
- // on that ability when developing against iOS.
43
- return [NSString stringWithFormat:@"%@:%@", host, port];
32
+ // Use explicit port from URL if available
33
+ if ([bundleURL port] != nullptr) {
34
+ return [NSString stringWithFormat:@"%@:%@", host, [bundleURL port]];
35
+ }
36
+
37
+ // Check environment variable
38
+ NSString *portStr = [[[NSProcessInfo processInfo] environment] objectForKey:@"RCT_METRO_PORT"];
39
+ if ((portStr != nullptr) && [portStr length] > 0) {
40
+ return [NSString stringWithFormat:@"%@:%@", host, portStr];
41
+ }
42
+
43
+ // For https, omit port — the scheme implies 443
44
+ if ([[bundleURL scheme] isEqualToString:@"https"]) {
45
+ return host;
46
+ }
47
+
48
+ // Default to 8081 for local development (Metro's default port)
49
+ return [NSString stringWithFormat:@"%@:%d", host, kDefaultMetroPort];
44
50
  }
45
51
 
46
52
  static NSString *getSHA256(NSString *string)
@@ -111,13 +117,15 @@ static NSURL *getInspectorDeviceUrl(NSURL *bundleURL)
111
117
  NSString *escapedInspectorDeviceId = [getInspectorDeviceId()
112
118
  stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];
113
119
 
114
- return [NSURL
115
- URLWithString:[NSString stringWithFormat:@"http://%@/inspector/device?name=%@&app=%@&device=%@&profiling=%@",
116
- getServerHost(bundleURL),
117
- escapedDeviceName,
118
- escapedAppName,
119
- escapedInspectorDeviceId,
120
- isProfilingBuild ? @"true" : @"false"]];
120
+ NSString *scheme = [bundleURL scheme] != nullptr ? [bundleURL scheme] : @"http";
121
+ return
122
+ [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/inspector/device?name=%@&app=%@&device=%@&profiling=%@",
123
+ scheme,
124
+ getServerHost(bundleURL),
125
+ escapedDeviceName,
126
+ escapedAppName,
127
+ escapedInspectorDeviceId,
128
+ isProfilingBuild ? @"true" : @"false"]];
121
129
  }
122
130
 
123
131
  @implementation RCTInspectorDevServerHelper
@@ -149,11 +157,14 @@ static void sendEventToAllConnections(NSString *event)
149
157
  NSString *escapedInspectorDeviceId = [getInspectorDeviceId()
150
158
  stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];
151
159
 
152
- NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/open-debugger?device=%@",
160
+ NSString *scheme = [bundleURL scheme] != nullptr ? [bundleURL scheme] : @"http";
161
+ NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/open-debugger?device=%@",
162
+ scheme,
153
163
  getServerHost(bundleURL),
154
164
  escapedInspectorDeviceId]];
155
165
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
156
166
  [request setHTTPMethod:@"POST"];
167
+ [[RCTDevSupportHttpHeaders sharedInstance] applyHeadersToRequest:request];
157
168
 
158
169
  [[[NSURLSession sharedSession]
159
170
  dataTaskWithRequest:request
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  #import "RCTInspectorNetworkHelper.h"
9
+ #import <React/RCTDevSupportHttpHeaders.h>
9
10
  #import <React/RCTLog.h>
10
11
 
11
12
  using ListenerBlock = void (^)(RCTInspectorNetworkListener *);
@@ -47,6 +48,7 @@ using ListenerBlock = void (^)(RCTInspectorNetworkListener *);
47
48
 
48
49
  NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
49
50
  [urlRequest setHTTPMethod:@"GET"];
51
+ [[RCTDevSupportHttpHeaders sharedInstance] applyHeadersToRequest:urlRequest];
50
52
  NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:urlRequest];
51
53
  __weak NSURLSessionDataTask *weakDataTask = dataTask;
52
54
 
@@ -9,6 +9,7 @@
9
9
 
10
10
  #if RCT_DEV || RCT_REMOTE_PROFILE
11
11
 
12
+ #import <React/RCTDevSupportHttpHeaders.h>
12
13
  #import <React/RCTInspector.h>
13
14
  #import <React/RCTInspectorPackagerConnection.h>
14
15
  #import <React/RCTLog.h>
@@ -36,7 +37,10 @@ NSString *NSStringFromUTF8StringView(std::string_view view)
36
37
  {
37
38
  if ((self = [super init]) != nullptr) {
38
39
  _delegate = delegate;
39
- _webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:NSStringFromUTF8StringView(url)]];
40
+ NSURL *requestURL = [NSURL URLWithString:NSStringFromUTF8StringView(url)];
41
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL];
42
+ [[RCTDevSupportHttpHeaders sharedInstance] applyHeadersToRequest:request];
43
+ _webSocket = [[SRWebSocket alloc] initWithURLRequest:request];
40
44
  _webSocket.delegate = self;
41
45
  [_webSocket open];
42
46
  }
@@ -2129,6 +2129,7 @@ public final class com/facebook/react/devsupport/StackTraceHelper$StackFrameImpl
2129
2129
  public fun toJSON ()Lorg/json/JSONObject;
2130
2130
  }
2131
2131
 
2132
+
2132
2133
  public abstract interface class com/facebook/react/devsupport/interfaces/BundleLoadCallback {
2133
2134
  public fun onError (Ljava/lang/Exception;)V
2134
2135
  public abstract fun onSuccess ()V
@@ -1,4 +1,4 @@
1
- VERSION_NAME=0.83.2
1
+ VERSION_NAME=0.83.4
2
2
  react.internal.publishingGroup=com.facebook.react
3
3
  react.internal.hermesPublishingGroup=com.facebook.hermes
4
4
 
@@ -12,10 +12,9 @@ import android.os.Looper
12
12
  import com.facebook.jni.HybridData
13
13
  import com.facebook.proguard.annotations.DoNotStrip
14
14
  import com.facebook.proguard.annotations.DoNotStripAny
15
+ import com.facebook.react.devsupport.inspector.DevSupportHttpClient
15
16
  import com.facebook.soloader.SoLoader
16
17
  import java.io.Closeable
17
- import java.util.concurrent.TimeUnit
18
- import okhttp3.OkHttpClient
19
18
  import okhttp3.Request
20
19
  import okhttp3.Response
21
20
  import okhttp3.WebSocket
@@ -78,12 +77,7 @@ internal class CxxInspectorPackagerConnection(
78
77
 
79
78
  /** Java implementation of the C++ InspectorPackagerConnectionDelegate interface. */
80
79
  private class DelegateImpl {
81
- private val httpClient =
82
- OkHttpClient.Builder()
83
- .connectTimeout(10, TimeUnit.SECONDS)
84
- .writeTimeout(10, TimeUnit.SECONDS)
85
- .readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read
86
- .build()
80
+ private val httpClient = DevSupportHttpClient.websocketClient
87
81
 
88
82
  private val handler = Handler(Looper.getMainLooper())
89
83
 
@@ -21,6 +21,7 @@ import com.facebook.react.bridge.ReactContext
21
21
  import com.facebook.react.common.ReactConstants
22
22
  import com.facebook.react.devsupport.InspectorFlags.getFuseboxEnabled
23
23
  import com.facebook.react.devsupport.InspectorFlags.getIsProfilingBuild
24
+ import com.facebook.react.devsupport.inspector.DevSupportHttpClient
24
25
  import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener
25
26
  import com.facebook.react.devsupport.interfaces.PackagerStatusCallback
26
27
  import com.facebook.react.modules.debug.interfaces.DeveloperSettings
@@ -39,7 +40,6 @@ import java.io.UnsupportedEncodingException
39
40
  import java.security.MessageDigest
40
41
  import java.security.NoSuchAlgorithmException
41
42
  import java.util.Locale
42
- import java.util.concurrent.TimeUnit
43
43
  import okhttp3.Call
44
44
  import okhttp3.Callback
45
45
  import okhttp3.OkHttpClient
@@ -79,19 +79,15 @@ public open class DevServerHelper(
79
79
  }
80
80
 
81
81
  public val websocketProxyURL: String
82
- get() = "ws://${packagerConnectionSettings.debugServerHost}/debugger-proxy?role=client"
82
+ get() =
83
+ "${DevSupportHttpClient.wsScheme(packagerConnectionSettings.debugServerHost)}://${packagerConnectionSettings.debugServerHost}/debugger-proxy?role=client"
83
84
 
84
85
  private enum class BundleType(val typeID: String) {
85
86
  BUNDLE("bundle"),
86
87
  MAP("map"),
87
88
  }
88
89
 
89
- private val client: OkHttpClient =
90
- OkHttpClient.Builder()
91
- .connectTimeout(HTTP_CONNECT_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
92
- .readTimeout(0, TimeUnit.MILLISECONDS)
93
- .writeTimeout(0, TimeUnit.MILLISECONDS)
94
- .build()
90
+ private val client: OkHttpClient = DevSupportHttpClient.httpClient
95
91
  private val bundleDownloader: BundleDownloader = BundleDownloader(client)
96
92
  private val packagerStatusCheck: PackagerStatusCheck = PackagerStatusCheck(client)
97
93
  private val packageName: String = applicationContext.packageName
@@ -129,7 +125,8 @@ public open class DevServerHelper(
129
125
  get() =
130
126
  String.format(
131
127
  Locale.US,
132
- "http://%s/inspector/device?name=%s&app=%s&device=%s&profiling=%b",
128
+ "%s://%s/inspector/device?name=%s&app=%s&device=%s&profiling=%b",
129
+ DevSupportHttpClient.httpScheme(packagerConnectionSettings.debugServerHost),
133
130
  packagerConnectionSettings.debugServerHost,
134
131
  Uri.encode(getFriendlyDeviceName()),
135
132
  Uri.encode(packageName),
@@ -292,7 +289,8 @@ public open class DevServerHelper(
292
289
  }
293
290
  return (String.format(
294
291
  Locale.US,
295
- "http://%s/%s.%s?platform=android&dev=%s&lazy=%s&minify=%s&app=%s&modulesOnly=%s&runModule=%s",
292
+ "%s://%s/%s.%s?platform=android&dev=%s&lazy=%s&minify=%s&app=%s&modulesOnly=%s&runModule=%s",
293
+ DevSupportHttpClient.httpScheme(host),
296
294
  host,
297
295
  mainModuleID,
298
296
  type.typeID,
@@ -367,7 +365,8 @@ public open class DevServerHelper(
367
365
  requestUrlBuilder.append(
368
366
  String.format(
369
367
  Locale.US,
370
- "http://%s/open-debugger?device=%s",
368
+ "%s://%s/open-debugger?device=%s",
369
+ DevSupportHttpClient.httpScheme(packagerConnectionSettings.debugServerHost),
371
370
  packagerConnectionSettings.debugServerHost,
372
371
  Uri.encode(inspectorDeviceId),
373
372
  )
@@ -397,7 +396,6 @@ public open class DevServerHelper(
397
396
  }
398
397
 
399
398
  private companion object {
400
- private const val HTTP_CONNECT_TIMEOUT_MS = 5000
401
399
  private const val DEBUGGER_MSG_DISABLE = "{ \"id\":1,\"method\":\"Debugger.disable\" }"
402
400
 
403
401
  private fun getSHA256(string: String): String {
@@ -446,7 +444,13 @@ public open class DevServerHelper(
446
444
  FLog.w(ReactConstants.TAG, "Resource path should not begin with `/`, removing it.")
447
445
  resourcePath = resourcePath.substring(1)
448
446
  }
449
- return String.format(Locale.US, "http://%s/%s", host, resourcePath)
447
+ return String.format(
448
+ Locale.US,
449
+ "%s://%s/%s",
450
+ DevSupportHttpClient.httpScheme(host),
451
+ host,
452
+ resourcePath,
453
+ )
450
454
  }
451
455
  }
452
456
  }
@@ -11,10 +11,10 @@ package com.facebook.react.devsupport
11
11
 
12
12
  import com.facebook.common.logging.FLog
13
13
  import com.facebook.react.common.ReactConstants
14
+ import com.facebook.react.devsupport.inspector.DevSupportHttpClient
14
15
  import com.facebook.react.devsupport.interfaces.PackagerStatusCallback
15
16
  import java.io.IOException
16
17
  import java.util.Locale
17
- import java.util.concurrent.TimeUnit
18
18
  import okhttp3.Call
19
19
  import okhttp3.Callback
20
20
  import okhttp3.OkHttpClient
@@ -22,22 +22,9 @@ import okhttp3.Request
22
22
  import okhttp3.Response
23
23
 
24
24
  /** Use this class to check if the JavaScript packager is running on the provided host. */
25
- internal class PackagerStatusCheck {
25
+ internal class PackagerStatusCheck(private val client: OkHttpClient) {
26
26
 
27
- private val client: OkHttpClient
28
-
29
- constructor() {
30
- client =
31
- OkHttpClient.Builder()
32
- .connectTimeout(HTTP_CONNECT_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
33
- .readTimeout(0, TimeUnit.MILLISECONDS)
34
- .writeTimeout(0, TimeUnit.MILLISECONDS)
35
- .build()
36
- }
37
-
38
- constructor(client: OkHttpClient) {
39
- this.client = client
40
- }
27
+ constructor() : this(DevSupportHttpClient.httpClient)
41
28
 
42
29
  fun run(host: String, callback: PackagerStatusCallback) {
43
30
  val statusURL = createPackagerStatusURL(host)
@@ -92,10 +79,14 @@ internal class PackagerStatusCheck {
92
79
 
93
80
  private companion object {
94
81
  private const val PACKAGER_OK_STATUS = "packager-status:running"
95
- private const val HTTP_CONNECT_TIMEOUT_MS = 5_000
96
- private const val PACKAGER_STATUS_URL_TEMPLATE = "http://%s/status"
82
+ private const val PACKAGER_STATUS_URL_TEMPLATE = "%s://%s/status"
97
83
 
98
84
  private fun createPackagerStatusURL(host: String): String =
99
- String.format(Locale.US, PACKAGER_STATUS_URL_TEMPLATE, host)
85
+ String.format(
86
+ Locale.US,
87
+ PACKAGER_STATUS_URL_TEMPLATE,
88
+ DevSupportHttpClient.httpScheme(host),
89
+ host,
90
+ )
100
91
  }
101
92
  }
@@ -33,12 +33,12 @@ import android.widget.TextView
33
33
  import com.facebook.common.logging.FLog
34
34
  import com.facebook.react.R
35
35
  import com.facebook.react.common.ReactConstants
36
+ import com.facebook.react.devsupport.inspector.DevSupportHttpClient
36
37
  import com.facebook.react.devsupport.interfaces.DevSupportManager
37
38
  import com.facebook.react.devsupport.interfaces.ErrorType
38
39
  import com.facebook.react.devsupport.interfaces.RedBoxHandler
39
40
  import com.facebook.react.devsupport.interfaces.StackFrame
40
41
  import okhttp3.MediaType
41
- import okhttp3.OkHttpClient
42
42
  import okhttp3.Request
43
43
  import okhttp3.RequestBody
44
44
  import org.json.JSONObject
@@ -165,7 +165,7 @@ internal class RedBoxContentView(
165
165
  .query(null)
166
166
  .build()
167
167
  .toString()
168
- val client = OkHttpClient()
168
+ val client = DevSupportHttpClient.httpClient
169
169
  for (frame in stackFrames) {
170
170
  val payload = stackFrameToJson(checkNotNull(frame)).toString()
171
171
  val body: RequestBody = RequestBody.create(JSON, payload)
@@ -0,0 +1,49 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ @file:Suppress("DEPRECATION_ERROR") // Conflicting okhttp versions
9
+
10
+ package com.facebook.react.devsupport.inspector
11
+
12
+ import com.facebook.react.modules.network.OkHttpClientProvider
13
+ import java.util.concurrent.TimeUnit
14
+ import okhttp3.OkHttpClient
15
+
16
+ /**
17
+ * Shared [OkHttpClient] instances for devsupport networking. Uses a single connection pool and
18
+ * dispatcher across all dev support HTTP and WebSocket usage.
19
+ */
20
+ internal object DevSupportHttpClient {
21
+ /** Client for HTTP requests: connect=5s, write=disabled, read=disabled. */
22
+ internal val httpClient: OkHttpClient =
23
+ OkHttpClientProvider.getOkHttpClient()
24
+ .newBuilder()
25
+ .connectTimeout(5, TimeUnit.SECONDS)
26
+ .writeTimeout(0, TimeUnit.MILLISECONDS)
27
+ .readTimeout(0, TimeUnit.MINUTES)
28
+ .build()
29
+
30
+ /** Client for WebSocket connections: connect=10s, write=10s, read=disabled. */
31
+ internal val websocketClient: OkHttpClient =
32
+ httpClient
33
+ .newBuilder()
34
+ .connectTimeout(10, TimeUnit.SECONDS)
35
+ .writeTimeout(10, TimeUnit.SECONDS)
36
+ .build()
37
+
38
+ /**
39
+ * Returns the appropriate HTTP scheme ("http" or "https") for the given host. Uses "https" when
40
+ * the host specifies port 443 explicitly (e.g. "example.com:443").
41
+ */
42
+ internal fun httpScheme(host: String): String = if (host.endsWith(":443")) "https" else "http"
43
+
44
+ /**
45
+ * Returns the appropriate WebSocket scheme ("ws" or "wss") for the given host. Uses "wss" when
46
+ * the host specifies port 443 explicitly (e.g. "example.com:443").
47
+ */
48
+ internal fun wsScheme(host: String): String = if (host.endsWith(":443")) "wss" else "ws"
49
+ }
@@ -10,26 +10,15 @@
10
10
  package com.facebook.react.devsupport.inspector
11
11
 
12
12
  import java.io.IOException
13
- import java.util.concurrent.TimeUnit
14
13
  import okhttp3.Call
15
14
  import okhttp3.Callback
16
- import okhttp3.OkHttpClient
17
15
  import okhttp3.Request
18
16
  import okhttp3.Response
19
17
 
20
18
  internal object InspectorNetworkHelper {
21
- private lateinit var client: OkHttpClient
22
19
 
23
20
  @JvmStatic
24
21
  fun loadNetworkResource(url: String, listener: InspectorNetworkRequestListener) {
25
- if (!::client.isInitialized) {
26
- client =
27
- OkHttpClient.Builder()
28
- .connectTimeout(10, TimeUnit.SECONDS)
29
- .writeTimeout(10, TimeUnit.SECONDS)
30
- .readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read
31
- .build()
32
- }
33
22
 
34
23
  val request =
35
24
  try {
@@ -40,7 +29,7 @@ internal object InspectorNetworkHelper {
40
29
  }
41
30
 
42
31
  // TODO(T196951523): Assign cancel function to listener
43
- val call = client.newCall(request)
32
+ val call = DevSupportHttpClient.httpClient.newCall(request)
44
33
 
45
34
  call.enqueue(
46
35
  object : Callback {
@@ -14,7 +14,7 @@ public object ReactNativeVersion {
14
14
  public val VERSION: Map<String, Any?> = mapOf(
15
15
  "major" to 0,
16
16
  "minor" to 83,
17
- "patch" to 2,
17
+ "patch" to 4,
18
18
  "prerelease" to null
19
19
  )
20
20
  }
@@ -22,6 +22,7 @@ import com.facebook.react.common.ReactConstants
22
22
  import com.facebook.react.module.annotations.ReactModule
23
23
  import com.facebook.react.modules.network.CustomClientBuilder
24
24
  import com.facebook.react.modules.network.ForwardingCookieHandler
25
+ import com.facebook.react.modules.network.OkHttpClientProvider
25
26
  import java.io.IOException
26
27
  import java.net.URI
27
28
  import java.net.URISyntaxException
@@ -80,7 +81,8 @@ public class WebSocketModule(context: ReactApplicationContext) :
80
81
  ) {
81
82
  val id = socketID.toInt()
82
83
  val okHttpBuilder =
83
- OkHttpClient.Builder()
84
+ OkHttpClientProvider.getOkHttpClient()
85
+ .newBuilder()
84
86
  .connectTimeout(10, TimeUnit.SECONDS)
85
87
  .writeTimeout(10, TimeUnit.SECONDS)
86
88
  .readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read
@@ -198,9 +200,6 @@ public class WebSocketModule(context: ReactApplicationContext) :
198
200
  }
199
201
  },
200
202
  )
201
-
202
- // Trigger shutdown of the dispatcher's executor so this process can exit cleanly
203
- client.dispatcher().executorService().shutdown()
204
203
  }
205
204
 
206
205
  override fun close(code: Double, reason: String?, socketID: Double) {
@@ -9,6 +9,7 @@ package com.facebook.react.packagerconnection
9
9
 
10
10
  import android.net.Uri
11
11
  import com.facebook.common.logging.FLog
12
+ import com.facebook.react.devsupport.inspector.DevSupportHttpClient
12
13
  import com.facebook.react.modules.systeminfo.AndroidInfoHelpers.getFriendlyDeviceName
13
14
  import com.facebook.react.packagerconnection.ReconnectingWebSocket.MessageCallback
14
15
  import okio.ByteString
@@ -28,7 +29,7 @@ public constructor(
28
29
  init {
29
30
  val url =
30
31
  Uri.Builder()
31
- .scheme("ws")
32
+ .scheme(DevSupportHttpClient.wsScheme(settings.debugServerHost))
32
33
  .encodedAuthority(settings.debugServerHost)
33
34
  .appendPath("message")
34
35
  .appendQueryParameter("device", getFriendlyDeviceName())
@@ -10,10 +10,9 @@ package com.facebook.react.packagerconnection
10
10
  import android.os.Handler
11
11
  import android.os.Looper
12
12
  import com.facebook.common.logging.FLog
13
+ import com.facebook.react.devsupport.inspector.DevSupportHttpClient
13
14
  import java.io.IOException
14
15
  import java.nio.channels.ClosedChannelException
15
- import java.util.concurrent.TimeUnit
16
- import okhttp3.OkHttpClient
17
16
  import okhttp3.Request
18
17
  import okhttp3.Response
19
18
  import okhttp3.WebSocket
@@ -40,12 +39,7 @@ public class ReconnectingWebSocket(
40
39
  }
41
40
 
42
41
  private val handler = Handler(Looper.getMainLooper())
43
- private val okHttpClient: OkHttpClient =
44
- OkHttpClient.Builder()
45
- .connectTimeout(10, TimeUnit.SECONDS)
46
- .writeTimeout(10, TimeUnit.SECONDS)
47
- .readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read
48
- .build()
42
+ private val okHttpClient = DevSupportHttpClient.websocketClient
49
43
  private var closed = false
50
44
  private var suppressConnectionErrors = false
51
45
  private var webSocket: WebSocket? = null
@@ -14,14 +14,14 @@
14
14
 
15
15
  #define REACT_NATIVE_VERSION_MAJOR 0
16
16
  #define REACT_NATIVE_VERSION_MINOR 83
17
- #define REACT_NATIVE_VERSION_PATCH 2
17
+ #define REACT_NATIVE_VERSION_PATCH 4
18
18
 
19
19
  namespace facebook::react {
20
20
 
21
21
  constexpr struct {
22
22
  int32_t Major = 0;
23
23
  int32_t Minor = 83;
24
- int32_t Patch = 2;
24
+ int32_t Patch = 4;
25
25
  std::string_view Prerelease = "";
26
26
  } ReactNativeVersion;
27
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native",
3
- "version": "0.83.2",
3
+ "version": "0.83.4",
4
4
  "description": "A framework for building native apps using React",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -160,13 +160,13 @@
160
160
  },
161
161
  "dependencies": {
162
162
  "@jest/create-cache-key-function": "^29.7.0",
163
- "@react-native/assets-registry": "0.83.2",
164
- "@react-native/codegen": "0.83.2",
165
- "@react-native/community-cli-plugin": "0.83.2",
166
- "@react-native/gradle-plugin": "0.83.2",
167
- "@react-native/js-polyfills": "0.83.2",
168
- "@react-native/normalize-colors": "0.83.2",
169
- "@react-native/virtualized-lists": "0.83.2",
163
+ "@react-native/assets-registry": "0.83.4",
164
+ "@react-native/codegen": "0.83.4",
165
+ "@react-native/community-cli-plugin": "0.83.4",
166
+ "@react-native/gradle-plugin": "0.83.4",
167
+ "@react-native/js-polyfills": "0.83.4",
168
+ "@react-native/normalize-colors": "0.83.4",
169
+ "@react-native/virtualized-lists": "0.83.4",
170
170
  "abort-controller": "^3.0.0",
171
171
  "anser": "^1.4.9",
172
172
  "ansi-regex": "^5.0.0",
@@ -12,6 +12,8 @@
12
12
 
13
13
  const {spawnSync} = require('child_process');
14
14
  const fs = require('fs');
15
+ const os = require('os');
16
+ const path = require('path');
15
17
  const yargs = require('yargs');
16
18
 
17
19
  const LAST_BUILD_FILENAME = 'React-Core-prebuilt/.last_build_configuration';
@@ -62,14 +64,66 @@ function replaceRNCoreConfiguration(
62
64
  const tarballURLPath = `${podsRoot}/ReactNativeCore-artifacts/reactnative-core-${version.toLowerCase()}-${configuration.toLowerCase()}.tar.gz`;
63
65
 
64
66
  const finalLocation = 'React-Core-prebuilt';
65
- console.log('Preparing the final location', finalLocation);
66
- fs.rmSync(finalLocation, {force: true, recursive: true});
67
- fs.mkdirSync(finalLocation, {recursive: true});
68
-
69
- console.log('Extracting the tarball', tarballURLPath);
70
- spawnSync('tar', ['-xf', tarballURLPath, '-C', finalLocation], {
71
- stdio: 'inherit',
72
- });
67
+
68
+ // Extract to a temporary directory on a regular filesystem first, then move
69
+ // into the final location. This avoids issues with partial tar extraction on
70
+ // certain filesystems (e.g. EdenFS) where extracting directly can silently
71
+ // produce incomplete results.
72
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rncore-'));
73
+ const tmpExtractDir = path.join(tmpDir, 'React-Core-prebuilt');
74
+ fs.mkdirSync(tmpExtractDir, {recursive: true});
75
+
76
+ try {
77
+ console.log('Extracting the tarball to temp dir', tarballURLPath);
78
+ const result = spawnSync(
79
+ 'tar',
80
+ ['-xf', tarballURLPath, '-C', tmpExtractDir],
81
+ {
82
+ stdio: 'inherit',
83
+ },
84
+ );
85
+
86
+ if (result.status !== 0) {
87
+ throw new Error(`tar extraction failed with exit code ${result.status}`);
88
+ }
89
+
90
+ // Verify extraction produced the expected xcframework structure
91
+ const xcfwPath = path.join(tmpExtractDir, 'React.xcframework');
92
+ const modulemapPath = path.join(xcfwPath, 'Modules', 'module.modulemap');
93
+ if (!fs.existsSync(modulemapPath)) {
94
+ throw new Error(
95
+ `Extraction verification failed: ${modulemapPath} not found`,
96
+ );
97
+ }
98
+
99
+ // Move from temp to final location
100
+ console.log('Preparing the final location', finalLocation);
101
+ fs.rmSync(finalLocation, {force: true, recursive: true});
102
+
103
+ // Use mv for an atomic-ish replacement. If the final location is on the
104
+ // same filesystem as tmpDir this is a rename; otherwise it falls back to
105
+ // copy + delete via spawnSync.
106
+ const mvResult = spawnSync('mv', [tmpExtractDir, finalLocation], {
107
+ stdio: 'inherit',
108
+ });
109
+
110
+ if (mvResult.status !== 0) {
111
+ // Fallback: copy recursively then remove temp
112
+ console.log('mv failed, falling back to cp -R');
113
+ fs.mkdirSync(finalLocation, {recursive: true});
114
+ const cpResult = spawnSync(
115
+ 'cp',
116
+ ['-R', tmpExtractDir + '/.', finalLocation],
117
+ {stdio: 'inherit'},
118
+ );
119
+ if (cpResult.status !== 0) {
120
+ throw new Error(`cp fallback failed with exit code ${cpResult.status}`);
121
+ }
122
+ }
123
+ } finally {
124
+ // Clean up temp directory
125
+ fs.rmSync(tmpDir, {force: true, recursive: true});
126
+ }
73
127
  }
74
128
 
75
129
  function updateLastBuildConfiguration(configuration /*: string */) {
@@ -47,8 +47,28 @@ buildscript {
47
47
  val properties = java.util.Properties()
48
48
  val propertiesToInherit = listOf("hermesV1Enabled", "react.hermesV1Enabled")
49
49
 
50
+ // We cannot assume that the node_modules are next to the android project, for example
51
+ // in monorepos, they might get hoisted.
52
+ // In a composite build, this included build can access the invoking (consumer) build
53
+ // via `gradle.parent`. We use its StartParameter to locate the app's `gradle.properties`:
54
+ // - `projectDir/gradle.properties` when Gradle is run with `-p <androidDir>`
55
+ // - `currentDir/gradle.properties` when run from the app android folder
56
+ // If neither exists, we keep the legacy RN fallback path below.
57
+
58
+ val parentGradle = gradle.parent
59
+ val parentProjectDir = parentGradle?.startParameter?.projectDir
60
+ val parentCurrentDir = parentGradle?.startParameter?.currentDir
61
+ val gradlePropertiesCandidates =
62
+ listOfNotNull(
63
+ parentProjectDir?.resolve("gradle.properties"),
64
+ parentCurrentDir?.resolve("gradle.properties"),
65
+ // Backward-compatible fallback for classic RN app layouts.
66
+ file("../../android/gradle.properties"),
67
+ )
68
+
50
69
  try {
51
- file("../../android/gradle.properties").inputStream().use { properties.load(it) }
70
+ val propertiesFile = gradlePropertiesCandidates.firstOrNull { it.exists() }
71
+ propertiesFile?.inputStream()?.use { properties.load(it) }
52
72
 
53
73
  gradle.rootProject {
54
74
  propertiesToInherit.forEach { property ->