From e28d15fca5a387bf4942f35443adad7fafbab28e Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Tue, 9 Jan 2024 10:41:13 -0800 Subject: [PATCH] RCTCxxInspectorPackagerConnection (#42037) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42037 Creates an Objective-C wrapper around the C++ version of `InspectorPackagerConnection` (introduced in D52134592), and uses it in React Native iOS apps (behind an internal flag that is off by default). In future work, the flag will be turned on by default, then deleted, and eventually the legacy `RCTInspectorPackagerConnection` code will be deleted from React Native. Changelog: [Internal] Reviewed By: huntie Differential Revision: D52225495 fbshipit-source-id: f1b9657ef0d665cf7892c15c34c5104e2777ec43 --- packages/react-native/React-Core.podspec | 1 + .../DevSupport/RCTInspectorDevServerHelper.h | 2 +- .../DevSupport/RCTInspectorDevServerHelper.mm | 15 +++- .../RCTCxxInspectorPackagerConnection.h | 17 ++++ .../RCTCxxInspectorPackagerConnection.mm | 69 +++++++++++++++ ...CTCxxInspectorPackagerConnectionDelegate.h | 41 +++++++++ ...TCxxInspectorPackagerConnectionDelegate.mm | 44 ++++++++++ .../RCTCxxInspectorWebSocketAdapter.h | 17 ++++ .../RCTCxxInspectorWebSocketAdapter.mm | 86 +++++++++++++++++++ .../React/Inspector/RCTInspector.mm | 2 +- .../RCTInspectorPackagerConnection.h | 5 +- .../React-jsinspector.podspec | 2 +- 12 files changed, 293 insertions(+), 8 deletions(-) create mode 100644 packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnection.h create mode 100644 packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnection.mm create mode 100644 packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.h create mode 100644 packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.mm create mode 100644 packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.h create mode 100644 packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm diff --git a/packages/react-native/React-Core.podspec b/packages/react-native/React-Core.podspec index ee4d76b8863..667b1f607ff 100644 --- a/packages/react-native/React-Core.podspec +++ b/packages/react-native/React-Core.podspec @@ -104,6 +104,7 @@ Pod::Spec.new do |s| ss.dependency "React-Core/Default", version ss.dependency "React-Core/RCTWebSocket", version ss.dependency "React-jsinspector", version + ss.private_header_files = "React/Inspector/RCTCxx*.h" end s.subspec "RCTWebSocket" do |ss| diff --git a/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.h b/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.h index 5be6db1c284..0c4dbc15cf4 100644 --- a/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.h +++ b/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.h @@ -15,7 +15,7 @@ @interface RCTInspectorDevServerHelper : NSObject -+ (RCTInspectorPackagerConnection *)connectWithBundleURL:(NSURL *)bundleURL; ++ (id)connectWithBundleURL:(NSURL *)bundleURL; + (void)disableDebugger; + (void)openDebugger:(NSURL *)bundleURL withErrorMessage:(NSString *)errorMessage; @end diff --git a/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.mm b/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.mm index a0c4b69be2e..2db91e64b0c 100644 --- a/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.mm +++ b/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.mm @@ -12,10 +12,12 @@ #import #import +#import #import #import #import +#import static NSString *const kDebuggerMsgDisable = @"{ \"id\":1,\"method\":\"Debugger.disable\" }"; @@ -107,7 +109,7 @@ static NSURL *getInspectorDeviceUrl(NSURL *bundleURL) RCT_NOT_IMPLEMENTED(-(instancetype)init) -static NSMutableDictionary *socketConnections = nil; +static NSMutableDictionary> *socketConnections = nil; static void sendEventToAllConnections(NSString *event) { @@ -146,7 +148,7 @@ static void sendEventToAllConnections(NSString *event) sendEventToAllConnections(kDebuggerMsgDisable); } -+ (RCTInspectorPackagerConnection *)connectWithBundleURL:(NSURL *)bundleURL ++ (id)connectWithBundleURL:(NSURL *)bundleURL { NSURL *inspectorURL = getInspectorDeviceUrl(bundleURL); @@ -158,9 +160,14 @@ static void sendEventToAllConnections(NSString *event) } NSString *key = [inspectorURL absoluteString]; - RCTInspectorPackagerConnection *connection = socketConnections[key]; + id connection = socketConnections[key]; if (!connection || !connection.isConnected) { - connection = [[RCTInspectorPackagerConnection alloc] initWithURL:inspectorURL]; + if (facebook::react::jsinspector_modern::InspectorFlags::getInstance().getEnableCxxInspectorPackagerConnection()) { + connection = [[RCTCxxInspectorPackagerConnection alloc] initWithURL:inspectorURL]; + } else { + connection = [[RCTInspectorPackagerConnection alloc] initWithURL:inspectorURL]; + } + socketConnections[key] = connection; [connection connect]; } diff --git a/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnection.h b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnection.h new file mode 100644 index 00000000000..93c4a457c9c --- /dev/null +++ b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnection.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import +#import "RCTInspectorPackagerConnection.h" + +#if RCT_DEV || RCT_REMOTE_PROFILE + +@interface RCTCxxInspectorPackagerConnection : NSObject +@end + +#endif diff --git a/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnection.mm b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnection.mm new file mode 100644 index 00000000000..53d465bc64c --- /dev/null +++ b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnection.mm @@ -0,0 +1,69 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#if RCT_DEV || RCT_REMOTE_PROFILE + +#import +#import +#import +#import +#import +#import + +#import +#import + +#import "RCTCxxInspectorPackagerConnection.h" +#import "RCTCxxInspectorPackagerConnectionDelegate.h" +#import "RCTCxxInspectorWebSocketAdapter.h" + +using namespace facebook::react::jsinspector_modern; +@interface RCTCxxInspectorPackagerConnection () { + std::unique_ptr _cxxImpl; +} +@end + +@implementation RCTCxxInspectorPackagerConnection + +RCT_NOT_IMPLEMENTED(-(instancetype)init) + +- (instancetype)initWithURL:(NSURL *)url +{ + if (self = [super init]) { + _cxxImpl = std::make_unique( + [url absoluteString].UTF8String, + [[NSBundle mainBundle] bundleIdentifier].UTF8String, + std::make_unique()); + } + return self; +} + +- (void)sendEventToAllConnections:(NSString *)event +{ + _cxxImpl->sendEventToAllConnections(event.UTF8String); +} + +- (bool)isConnected +{ + return _cxxImpl->isConnected(); +} + +- (void)connect +{ + _cxxImpl->connect(); +} + +- (void)closeQuietly +{ + _cxxImpl->closeQuietly(); +} + +@end + +#endif diff --git a/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.h b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.h new file mode 100644 index 00000000000..d922ef17216 --- /dev/null +++ b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTCxxInspectorWebSocketAdapter.h" + +#import + +#import +#import +#import + +namespace facebook::react::jsinspector_modern { +/** + * Glue between C++ and Objective-C for InspectorPackagerConnectionDelegate. + */ +class RCTCxxInspectorPackagerConnectionDelegate + : public InspectorPackagerConnectionDelegate { + class WebSocket : public IWebSocket { + public: + WebSocket(RCTCxxInspectorWebSocketAdapter* adapter); + virtual void send(std::string_view message) override; + virtual ~WebSocket() override; + + private: + RCTCxxInspectorWebSocketAdapter* _adapter; + }; + + public: + virtual std::unique_ptr connectWebSocket( + const std::string& url, + std::weak_ptr delegate) override; + + virtual void scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs) override; +}; +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.mm b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.mm new file mode 100644 index 00000000000..6fa537dfd19 --- /dev/null +++ b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.mm @@ -0,0 +1,44 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTCxxInspectorPackagerConnectionDelegate.h" + +#import + +namespace facebook::react::jsinspector_modern { +RCTCxxInspectorPackagerConnectionDelegate::WebSocket::WebSocket(RCTCxxInspectorWebSocketAdapter *adapter) + : _adapter(adapter) +{ +} + +void RCTCxxInspectorPackagerConnectionDelegate::WebSocket::send(std::string_view message) +{ + [_adapter send:message]; +} + +RCTCxxInspectorPackagerConnectionDelegate::WebSocket::~WebSocket() +{ + [_adapter close]; +} + +std::unique_ptr RCTCxxInspectorPackagerConnectionDelegate::connectWebSocket( + const std::string &url, + std::weak_ptr delegate) +{ + auto *adapter = [[RCTCxxInspectorWebSocketAdapter alloc] initWithURL:url delegate:delegate]; + return std::make_unique(adapter); +} + +void RCTCxxInspectorPackagerConnectionDelegate::scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs) +{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayMs.count() * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ + callback(); + }); +} +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.h b/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.h new file mode 100644 index 00000000000..a06d83413c6 --- /dev/null +++ b/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import +#import + +@interface RCTCxxInspectorWebSocketAdapter : NSObject +- (instancetype)initWithURL:(const std::string &)url + delegate:(std::weak_ptr)delegate; +- (void)send:(std::string_view)message; +- (void)close; +@end diff --git a/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm b/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm new file mode 100644 index 00000000000..85874748590 --- /dev/null +++ b/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm @@ -0,0 +1,86 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import +#import +#import +#import +#import +#import +#import +#import "RCTCxxInspectorWebSocketAdapter.h" + +using namespace facebook::react::jsinspector_modern; + +namespace { +NSString *NSStringFromUTF8StringView(std::string_view view) +{ + return [[NSString alloc] initWithBytes:(const char *)view.data() length:view.size() encoding:NSUTF8StringEncoding]; +} +} +@interface RCTCxxInspectorWebSocketAdapter () { + std::weak_ptr _delegate; + SRWebSocket *_webSocket; +} +@end + +@implementation RCTCxxInspectorWebSocketAdapter +- (instancetype)initWithURL:(const std::string &)url delegate:(std::weak_ptr)delegate +{ + if ((self = [super init])) { + _delegate = delegate; + _webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:NSStringFromUTF8StringView(url)]]; + _webSocket.delegate = self; + [_webSocket open]; + } + return self; +} + +- (void)send:(std::string_view)message +{ + __weak RCTCxxInspectorWebSocketAdapter *weakSelf = self; + NSString *messageStr = NSStringFromUTF8StringView(message); + dispatch_async(dispatch_get_main_queue(), ^{ + RCTCxxInspectorWebSocketAdapter *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf->_webSocket send:messageStr]; + } + }); +} + +- (void)close +{ + [_webSocket closeWithCode:1000 reason:@"End of session"]; +} + +- (void)webSocket:(__unused SRWebSocket *)webSocket didFailWithError:(NSError *)error +{ + if (auto delegate = _delegate.lock()) { + delegate->didFailWithError([error code], [error description].UTF8String); + } +} + +- (void)webSocket:(__unused SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)message +{ + if (auto delegate = _delegate.lock()) { + delegate->didReceiveMessage([message UTF8String]); + } +} + +- (void)webSocket:(__unused SRWebSocket *)webSocket + didCloseWithCode:(__unused NSInteger)code + reason:(__unused NSString *)reason + wasClean:(__unused BOOL)wasClean +{ + if (auto delegate = _delegate.lock()) { + delegate->didClose(); + } +} + +@end diff --git a/packages/react-native/React/Inspector/RCTInspector.mm b/packages/react-native/React/Inspector/RCTInspector.mm index 0c47f6da779..1c99fcbccf1 100644 --- a/packages/react-native/React/Inspector/RCTInspector.mm +++ b/packages/react-native/React/Inspector/RCTInspector.mm @@ -9,7 +9,7 @@ #if RCT_DEV || RCT_REMOTE_PROFILE -#include +#import #import #import diff --git a/packages/react-native/React/Inspector/RCTInspectorPackagerConnection.h b/packages/react-native/React/Inspector/RCTInspectorPackagerConnection.h index 20c67001523..72e279dce30 100644 --- a/packages/react-native/React/Inspector/RCTInspectorPackagerConnection.h +++ b/packages/react-native/React/Inspector/RCTInspectorPackagerConnection.h @@ -10,7 +10,7 @@ #if RCT_DEV || RCT_REMOTE_PROFILE -@interface RCTInspectorPackagerConnection : NSObject +@protocol RCTInspectorPackagerConnectionProtocol - (instancetype)initWithURL:(NSURL *)url; - (bool)isConnected; @@ -19,6 +19,9 @@ - (void)sendEventToAllConnections:(NSString *)event; @end +@interface RCTInspectorPackagerConnection : NSObject +@end + @interface RCTInspectorRemoteConnection : NSObject - (void)onMessage:(NSString *)message; - (void)onDisconnect; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec b/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec index 2f8168ba61e..5c95d70957e 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec @@ -30,7 +30,7 @@ Pod::Spec.new do |s| s.platforms = min_supported_versions s.source = source s.source_files = "*.{cpp,h}" - s.header_dir = 'jsinspector' + s.header_dir = 'jsinspector-modern' s.compiler_flags = folly_compiler_flags s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/..\" \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\"",