react-native-tvos 0.83.2-0 → 0.83.4-0

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 (64) hide show
  1. package/Libraries/Components/ScrollView/AndroidHorizontalScrollViewNativeComponent.js +1 -0
  2. package/Libraries/Components/ScrollView/ScrollView.d.ts +2 -1
  3. package/Libraries/Components/ScrollView/ScrollView.js +8 -1
  4. package/Libraries/Components/ScrollView/ScrollViewNativeComponent.js +2 -0
  5. package/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js +2 -1
  6. package/Libraries/Components/TV/TVViewPropTypes.js +7 -0
  7. package/Libraries/Components/View/View.js +6 -0
  8. package/Libraries/Core/ReactNativeVersion.js +1 -1
  9. package/Libraries/Core/setUpReactDevTools.js +23 -6
  10. package/Libraries/NativeComponent/TVViewConfig.js +1 -0
  11. package/Libraries/Network/RCTHTTPRequestHandler.h +9 -0
  12. package/Libraries/Network/RCTHTTPRequestHandler.mm +15 -1
  13. package/Libraries/Pressability/Pressability.js +7 -0
  14. package/Libraries/WebSocket/RCTReconnectingWebSocket.m +4 -1
  15. package/React/Base/RCTBundleURLProvider.mm +5 -3
  16. package/React/Base/RCTDevSupportHttpHeaders.h +24 -0
  17. package/React/Base/RCTDevSupportHttpHeaders.m +65 -0
  18. package/React/Base/RCTMultipartDataTask.h +9 -0
  19. package/React/Base/RCTMultipartDataTask.m +16 -1
  20. package/React/Base/RCTVersion.m +1 -1
  21. package/React/CoreModules/RCTWebSocketModule.h +6 -0
  22. package/React/CoreModules/RCTWebSocketModule.mm +14 -1
  23. package/React/DevSupport/RCTInspectorDevServerHelper.mm +33 -22
  24. package/React/DevSupport/RCTInspectorNetworkHelper.mm +2 -0
  25. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.h +1 -0
  26. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm +3 -0
  27. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +110 -4
  28. package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +2 -0
  29. package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +31 -4
  30. package/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm +5 -1
  31. package/ReactAndroid/api/ReactAndroid.api +1 -0
  32. package/ReactAndroid/gradle.properties +1 -1
  33. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.kt +2 -8
  34. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.kt +17 -13
  35. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/PackagerStatusCheck.kt +10 -19
  36. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxContentView.kt +2 -2
  37. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/DevSupportHttpClient.kt +49 -0
  38. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/InspectorNetworkHelper.kt +1 -12
  39. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  40. package/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt +3 -4
  41. package/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/JSPackagerClient.kt +2 -1
  42. package/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/ReconnectingWebSocket.kt +2 -8
  43. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +49 -5
  44. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.kt +7 -0
  45. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +46 -4
  46. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt +69 -0
  47. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.kt +7 -0
  48. package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt +1 -0
  49. package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt +4 -0
  50. package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
  51. package/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp +10 -0
  52. package/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.h +1 -0
  53. package/ReactCommon/react/renderer/components/scrollview/conversions.h +6 -0
  54. package/ReactCommon/react/renderer/components/scrollview/platform/android/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp +18 -1
  55. package/ReactCommon/react/renderer/components/scrollview/platform/android/react/renderer/components/scrollview/HostPlatformScrollViewProps.h +2 -0
  56. package/ReactCommon/react/renderer/components/scrollview/primitives.h +1 -1
  57. package/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +11 -1
  58. package/ReactCommon/react/renderer/components/view/BaseViewProps.h +4 -0
  59. package/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp +18 -0
  60. package/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.h +1 -0
  61. package/package.json +8 -8
  62. package/scripts/replace-rncore-version.js +62 -8
  63. package/settings.gradle.kts +21 -1
  64. package/types/public/ReactNativeTVTypes.d.ts +19 -2
@@ -30,6 +30,7 @@
30
30
  #import <React/RCTTVRemoteHandler.h>
31
31
  #import <React/RCTTVNavigationEventNotification.h>
32
32
  #import "React/RCTI18nUtil.h"
33
+ #import "RCTViewComponentView.h"
33
34
  #endif
34
35
 
35
36
  using namespace facebook::react;
@@ -40,6 +41,7 @@ static NSString *kOnScrollEndEvent = @"onScrollEnded";
40
41
 
41
42
  static const CGFloat kClippingLeeway = 44.0;
42
43
  static const float TV_DEFAULT_SWIPE_DURATION = 0.3;
44
+ static const CGPoint NO_PREFERRED_CONTENT_OFFSET = CGPointMake(CGFLOAT_MIN, CGFLOAT_MIN);
43
45
 
44
46
  static UIScrollViewKeyboardDismissMode RCTUIKeyboardDismissModeFromProps(const ScrollViewProps &props)
45
47
  {
@@ -97,6 +99,8 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
97
99
  RCTScrollableProtocol,
98
100
  RCTEnhancedScrollViewOverridingDelegate>
99
101
 
102
+ @property (nonatomic, assign) CGPoint preferredContentOffset;
103
+
100
104
  @end
101
105
 
102
106
  @implementation RCTScrollViewComponentView {
@@ -172,6 +176,7 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
172
176
  _endDraggingSensitivityMultiplier = 1;
173
177
 
174
178
  _tvRemoteGestureRecognizers = [NSMutableDictionary new];
179
+ _preferredContentOffset = NO_PREFERRED_CONTENT_OFFSET;
175
180
  }
176
181
 
177
182
  return self;
@@ -465,6 +470,12 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
465
470
  scrollView.keyboardDismissMode = RCTUIKeyboardDismissModeFromProps(newScrollViewProps);
466
471
  }
467
472
 
473
+ #if TARGET_OS_TV
474
+ if (oldScrollViewProps.snapToAlignment != newScrollViewProps.snapToAlignment) {
475
+ scrollView.scrollSnapEnabled = newScrollViewProps.snapToAlignment == ScrollViewSnapToAlignment::Item;
476
+ }
477
+ #endif
478
+
468
479
  [super updateProps:props oldProps:oldProps];
469
480
  }
470
481
 
@@ -722,7 +733,10 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
722
733
  withVelocity:(CGPoint)velocity
723
734
  targetContentOffset:(inout CGPoint *)targetContentOffset
724
735
  {
725
- if (fabs(_endDraggingSensitivityMultiplier - 1) > 0.0001f) {
736
+ if (!CGPointEqualToPoint(self.preferredContentOffset, NO_PREFERRED_CONTENT_OFFSET))
737
+ {
738
+ *targetContentOffset = self.preferredContentOffset;
739
+ } else if (fabs(_endDraggingSensitivityMultiplier - 1) > 0.0001f) {
726
740
  if (targetContentOffset->y > 0) {
727
741
  const CGFloat travel = targetContentOffset->y - scrollView.contentOffset.y;
728
742
  targetContentOffset->y = scrollView.contentOffset.y + travel * _endDraggingSensitivityMultiplier;
@@ -1137,19 +1151,109 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
1137
1151
  #pragma mark Apple TV swipe and focus handling
1138
1152
 
1139
1153
  #if TARGET_OS_TV
1154
+ // Focus marker helper: traverses view hierarchy to find scrollSnapAlign prop
1155
+ // Returns the view that has the property via the output parameter
1156
+ - (NSString *)findScrollSnapAlignInView:(UIView *)view foundView:(UIView **)outView
1157
+ {
1158
+ UIView *testView = view;
1159
+ UIView *snapTarget;
1160
+ NSString *marker;
1161
+
1162
+ while (testView && testView != self) {
1163
+ if (![testView isKindOfClass:RCTViewComponentView.class])
1164
+ {
1165
+ testView = [testView superview];
1166
+ continue;
1167
+ }
1168
+ RCTViewComponentView *componentView = (RCTViewComponentView *)testView;
1169
+
1170
+ const auto &viewProps = static_cast<const facebook::react::BaseViewProps &>(*componentView.props);
1171
+ if (viewProps.scrollSnapAlign.has_value() && !viewProps.scrollSnapAlign.value().empty()) {
1172
+ marker = [NSString stringWithUTF8String:viewProps.scrollSnapAlign.value().c_str()];
1173
+ snapTarget = componentView;
1174
+ }
1175
+
1176
+ testView = [testView superview];
1177
+ }
1178
+ *outView = snapTarget;
1179
+ return marker;
1180
+ }
1181
+
1182
+ - (void)_handleScrollSnapForFocusedView:(UIView *)focusedView
1183
+ {
1184
+ const auto &scrollProps = static_cast<const ScrollViewProps &>(*_props);
1185
+ UIView *snapAlignView = nil;
1186
+ NSString *scrollSnapAlign = [self findScrollSnapAlignInView:focusedView foundView:&snapAlignView];
1187
+ if (scrollSnapAlign == nil || snapAlignView == nil) {
1188
+ return;
1189
+ }
1190
+
1191
+ RCTEnhancedScrollView *scrollView = (RCTEnhancedScrollView *)_scrollView;
1192
+ CGRect focusedFrame = [snapAlignView convertRect:snapAlignView.bounds toView:_scrollView];
1193
+ CGFloat targetOffset;
1194
+ CGFloat snapToItemPadding = scrollProps.snapToItemPadding;
1195
+
1196
+ BOOL isHorizontalSnap = _scrollView.contentSize.width > self.frame.size.width;
1197
+ // Determine axis-specific properties
1198
+ CGFloat viewportSize, focusedOrigin, focusedSize, currentOffset, maxContentSize;
1199
+ if (isHorizontalSnap) {
1200
+ viewportSize = scrollView.bounds.size.width;
1201
+ focusedOrigin = focusedFrame.origin.x;
1202
+ focusedSize = focusedFrame.size.width;
1203
+ currentOffset = scrollView.contentOffset.x;
1204
+ maxContentSize = scrollView.contentSize.width;
1205
+ } else {
1206
+ viewportSize = scrollView.bounds.size.height;
1207
+ focusedOrigin = focusedFrame.origin.y;
1208
+ focusedSize = focusedFrame.size.height;
1209
+ currentOffset = scrollView.contentOffset.y;
1210
+ maxContentSize = scrollView.contentSize.height;
1211
+ }
1212
+ // Calculate target offset based on scrollSnapAlign (unified for both axes)
1213
+ if ([scrollSnapAlign isEqualToString:@"start"]) {
1214
+ targetOffset = focusedOrigin - snapToItemPadding;
1215
+ } else if ([scrollSnapAlign isEqualToString:@"center"]) {
1216
+ CGFloat viewportCenter = viewportSize / 2;
1217
+ CGFloat focusedCenter = focusedOrigin + (focusedSize / 2);
1218
+ targetOffset = focusedCenter - viewportCenter + (snapToItemPadding / 2);
1219
+ } else if ([scrollSnapAlign isEqualToString:@"end"]) {
1220
+ targetOffset = (focusedOrigin + focusedSize) - viewportSize + snapToItemPadding;
1221
+ } else {
1222
+ targetOffset = currentOffset;
1223
+ }
1224
+
1225
+ // Apply snap-to-interval if configured
1226
+ if (scrollView.snapToInterval > 0) {
1227
+ CGFloat interval = scrollView.snapToInterval;
1228
+ targetOffset = floor(targetOffset / interval) * interval;
1229
+ }
1230
+
1231
+ // Clamp to valid range
1232
+ CGFloat maxOffset = MAX(maxContentSize - viewportSize, 0);
1233
+ targetOffset = MAX(0, MIN(targetOffset, maxOffset));
1234
+ CGPoint targetContentOffset = isHorizontalSnap
1235
+ ? CGPointMake(targetOffset, scrollView.contentOffset.y)
1236
+ : CGPointMake(scrollView.contentOffset.x, targetOffset);
1237
+ self.preferredContentOffset = targetContentOffset;
1238
+ [_scrollView setContentOffset:targetContentOffset animated:YES];
1239
+ }
1240
+
1140
1241
  - (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context
1141
1242
  withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
1142
1243
  {
1143
- if (context.previouslyFocusedView == context.nextFocusedView || !_props->isTVSelectable) {
1244
+ self.preferredContentOffset = NO_PREFERRED_CONTENT_OFFSET;
1245
+ const auto &scrollProps = static_cast<const ScrollViewProps &>(*_props);
1246
+ BOOL hasItemSnapAlignment = scrollProps.snapToAlignment == ScrollViewSnapToAlignment::Item;
1247
+ if (context.previouslyFocusedView == context.nextFocusedView || (!_props->isTVSelectable && !hasItemSnapAlignment)) {
1144
1248
  return;
1145
1249
  }
1146
- if (context.nextFocusedView == self) {
1250
+ if (_props->isTVSelectable && context.nextFocusedView == self) {
1147
1251
  [self becomeFirstResponder];
1148
1252
  [self addSwipeGestureRecognizers];
1149
1253
  // if we enter the scroll view from different view then block first touch event since it is the event that triggered the focus
1150
1254
  _blockFirstTouch = (unsigned long)context.focusHeading != 0;
1151
1255
  [self addArrowsListeners];
1152
- } else if (context.previouslyFocusedView == self) {
1256
+ } else if (_props->isTVSelectable && context.previouslyFocusedView == self) {
1153
1257
  [self removeArrowsListeners];
1154
1258
  [self removeSwipeGestureRecognizers];
1155
1259
  [self resignFirstResponder];
@@ -1171,6 +1275,8 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
1171
1275
  [self scrollToHorizontalOffset:scrollView.contentSize.width];
1172
1276
  }
1173
1277
  }
1278
+ } else if ([context.nextFocusedView isDescendantOfView:_scrollView]) {
1279
+ [self _handleScrollSnapForFocusedView:context.nextFocusedView];
1174
1280
  }
1175
1281
  }
1176
1282
 
@@ -80,6 +80,8 @@ NS_ASSUME_NONNULL_BEGIN
80
80
  @property(nonatomic, nullable) UIFocusGuide *focusGuideLeft;
81
81
  @property(nonatomic, nullable) UIFocusGuide *focusGuideRight;
82
82
  @property(nonatomic, nullable, strong) RCTTVRemoteSelectHandler *tvRemoteSelectHandler;
83
+
84
+ - (NSString *)scrollSnapAlign;
83
85
  #endif
84
86
 
85
87
  /**
@@ -87,7 +87,9 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
87
87
  BOOL _trapFocusRight;
88
88
  NSArray* _focusDestinations;
89
89
  id<UIFocusItem> _previouslyFocusedItem;
90
+ NSString *_scrollSnapAlign;
90
91
  RCTSwiftUIContainerViewWrapper *_swiftUIWrapper;
92
+ BOOL _shouldFocusOnMount;
91
93
  }
92
94
 
93
95
  #ifdef RCT_DYNAMIC_FRAMEWORKS
@@ -113,6 +115,8 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
113
115
  _tvParallaxProperties.pressMagnification = 1.0f;
114
116
  _tvParallaxProperties.pressDuration = 0.3f;
115
117
  _tvParallaxProperties.pressDelay = 0.0f;
118
+
119
+ _shouldFocusOnMount = false;
116
120
  #else
117
121
  self.multipleTouchEnabled = YES;
118
122
  #endif
@@ -170,6 +174,15 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
170
174
 
171
175
  #pragma mark - Apple TV methods
172
176
 
177
+ - (void)didMoveToWindow {
178
+ [super didMoveToWindow];
179
+
180
+ if (self->_shouldFocusOnMount) {
181
+ self->_shouldFocusOnMount = false;
182
+ [self focusSelf];
183
+ }
184
+ }
185
+
173
186
  - (RCTSurfaceHostingProxyRootView *)containingRootView
174
187
  {
175
188
  UIView *rootview = self;
@@ -205,10 +218,8 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
205
218
  if (!focusedSync) {
206
219
  // `focusSelf` function relies on `rootView` which may not be present on the first render.
207
220
  // `focusSelf` fails and returns `false` in that case. We try re-executing the same action
208
- // by putting it to the main queue to make sure it runs after UI creation is completed.
209
- dispatch_async(dispatch_get_main_queue(), ^{
210
- [self focusSelf];
211
- });
221
+ // when the view has moved to the window. This is tracked by the `_shouldFocusOnMount` flag.
222
+ self->_shouldFocusOnMount = true;
212
223
  }
213
224
  }
214
225
 
@@ -431,6 +442,13 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
431
442
  _motionEffectsAdded = NO;
432
443
  }
433
444
 
445
+ - (NSString *)scrollSnapAlign
446
+ {
447
+ #if TARGET_OS_TV
448
+ return _scrollSnapAlign;
449
+ #endif
450
+ return nil;
451
+ }
434
452
 
435
453
  - (BOOL)isTVFocusGuide
436
454
  {
@@ -1160,6 +1178,15 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
1160
1178
  _trapFocusDown = newViewProps.trapFocusDown;
1161
1179
  _trapFocusLeft = newViewProps.trapFocusLeft;
1162
1180
  _trapFocusRight = newViewProps.trapFocusRight;
1181
+
1182
+ // `scrollSnapAlign`
1183
+ if (oldViewProps.scrollSnapAlign != newViewProps.scrollSnapAlign) {
1184
+ if (newViewProps.scrollSnapAlign.has_value()) {
1185
+ _scrollSnapAlign = [[NSString alloc] initWithUTF8String:newViewProps.scrollSnapAlign.value().c_str()];
1186
+ } else {
1187
+ _scrollSnapAlign = nil;
1188
+ }
1189
+ }
1163
1190
  #endif
1164
1191
 
1165
1192
  _needsInvalidateLayer = _needsInvalidateLayer || needsInvalidateLayer;
@@ -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-0
1
+ VERSION_NAME=0.83.4-0
2
2
  react.internal.publishingGroup=io.github.react-native-tvos
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 "0"
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