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\"",