diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index d5b04e79708..440ff3e2b8f 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -733,6 +733,7 @@ public abstract interface class com/facebook/react/bridge/Inspector$RemoteConnec } public class com/facebook/react/bridge/InspectorFlags { + public static fun getEnableCxxInspectorPackagerConnection ()Z public static fun getEnableModernCDPRegistry ()Z } @@ -2197,7 +2198,7 @@ public abstract interface class com/facebook/react/devsupport/HMRClient : com/fa public abstract fun setup (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZ)V } -public class com/facebook/react/devsupport/InspectorPackagerConnection { +public class com/facebook/react/devsupport/InspectorPackagerConnection : com/facebook/react/devsupport/IInspectorPackagerConnection { public fun (Ljava/lang/String;Ljava/lang/String;)V public fun closeQuietly ()V public fun connect ()V diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 291c0ed7519..19537c13756 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -524,6 +524,7 @@ android { "hermesinstancejni", "uimanagerjni", "jscinstance", + "react_devsupportjni", // prefab targets "reactnativejni", "react_render_debug", diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/InspectorFlags.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/InspectorFlags.java index 2a76037313e..1e7b7bb750c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/InspectorFlags.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/InspectorFlags.java @@ -19,5 +19,8 @@ public class InspectorFlags { @DoNotStrip public static native boolean getEnableModernCDPRegistry(); + @DoNotStrip + public static native boolean getEnableCxxInspectorPackagerConnection(); + private InspectorFlags() {} } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java new file mode 100644 index 00000000000..128fb44551b --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java @@ -0,0 +1,142 @@ +/* + * 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. + */ + +package com.facebook.react.devsupport; + +import android.os.Handler; +import android.os.Looper; +import androidx.annotation.Nullable; +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import java.io.Closeable; +import java.util.OptionalInt; +import java.util.concurrent.TimeUnit; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; + +/** Java wrapper around a C++ InspectorPackagerConnection. */ +/* package */ class CxxInspectorPackagerConnection implements IInspectorPackagerConnection { + static { + DevSupportSoLoader.staticInit(); + } + + @DoNotStrip private final HybridData mHybridData; + + public CxxInspectorPackagerConnection(String url, String packageName) { + mHybridData = initHybrid(url, packageName, new DelegateImpl()); + } + + private static native HybridData initHybrid( + String url, String packageName, DelegateImpl delegate); + + public native void connect(); + + public native void closeQuietly(); + + public native void sendEventToAllConnections(String event); + + /** Java wrapper around a C++ IWebSocketDelegate, allowing us to call the interface from Java. */ + @DoNotStrip + private static class WebSocketDelegate implements Closeable { + private final HybridData mHybridData; + + public native void didFailWithError(OptionalInt posixCode, String error); + + public native void didReceiveMessage(String message); + + public native void didClose(); + + /** + * Release the C++ part of the hybrid WebSocketDelegate object. This should be called when the + * delegate is not needed anymore ( = the socket will not send more events). + */ + @Override + public void close() { + mHybridData.resetNative(); + } + + @DoNotStrip + private WebSocketDelegate(HybridData hybridData) { + mHybridData = hybridData; + } + } + + /** + * Java counterpart of the C++ IWebSocket interface, allowing us to implement the interface in + * Java. + */ + private interface IWebSocket extends Closeable { + void send(String message); + + /** + * Close the WebSocket connection. NOTE: There is no close() method in the C++ interface. + * Instead, this method is called when the IWebSocket is destroyed on the C++ side. + */ + void close(); + } + + /** Java implementation of the C++ InspectorPackagerConnectionDelegate interface. */ + private static class DelegateImpl { + private final OkHttpClient mHttpClient = + new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read + .build(); + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + public IWebSocket connectWebSocket(String url, WebSocketDelegate delegate) { + Request request = new Request.Builder().url(url).build(); + final WebSocket webSocket = + mHttpClient.newWebSocket( + request, + new WebSocketListener() { + @Override + public void onFailure(WebSocket _unused, Throwable t, @Nullable Response response) { + @Nullable String message = t.getMessage(); + delegate.didFailWithError( + OptionalInt.empty(), message != null ? message : ""); + // "No further calls to this listener will be made." -OkHttp docs for + // WebSocketListener.onFailure + delegate.close(); + } + + @Override + public void onMessage(WebSocket _unused, String text) { + delegate.didReceiveMessage(text); + } + + @Override + public void onClosed(WebSocket _unused, int code, String reason) { + delegate.didClose(); + // "No further calls to this listener will be made." -OkHttp docs for + // WebSocketListener.onClosed + delegate.close(); + } + }); + return new IWebSocket() { + @Override + public void send(String message) { + webSocket.send(message); + } + + @Override + public void close() { + webSocket.close(1000, "End of session"); + } + }; + } + + public void scheduleCallback(Runnable runnable, long delayMs) { + mHandler.postDelayed(runnable, delayMs); + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 1419c7f1696..31eb28004a0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -14,6 +14,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.InspectorFlags; import com.facebook.react.bridge.ReactContext; import com.facebook.react.common.ReactConstants; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; @@ -107,7 +108,7 @@ public class DevServerHelper { private final String mPackageName; private @Nullable JSPackagerClient mPackagerClient; - private @Nullable InspectorPackagerConnection mInspectorPackagerConnection; + private @Nullable IInspectorPackagerConnection mInspectorPackagerConnection; public DevServerHelper( DeveloperSettings developerSettings, @@ -210,8 +211,13 @@ public class DevServerHelper { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - mInspectorPackagerConnection = - new InspectorPackagerConnection(getInspectorDeviceUrl(), mPackageName); + if (InspectorFlags.getEnableCxxInspectorPackagerConnection()) { + mInspectorPackagerConnection = + new CxxInspectorPackagerConnection(getInspectorDeviceUrl(), mPackageName); + } else { + mInspectorPackagerConnection = + new InspectorPackagerConnection(getInspectorDeviceUrl(), mPackageName); + } mInspectorPackagerConnection.connect(); return null; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportSoLoader.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportSoLoader.java new file mode 100644 index 00000000000..4f04fc2f492 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportSoLoader.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +package com.facebook.react.devsupport; + +import com.facebook.soloader.SoLoader; + +class DevSupportSoLoader { + private static volatile boolean sDidInit = false; + + public static synchronized void staticInit() { + if (sDidInit) { + return; + } + SoLoader.loadLibrary("react_devsupportjni"); + sDidInit = true; + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/IInspectorPackagerConnection.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/IInspectorPackagerConnection.java new file mode 100644 index 00000000000..59afe06a6f4 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/IInspectorPackagerConnection.java @@ -0,0 +1,16 @@ +/* + * 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. + */ + +package com.facebook.react.devsupport; + +/* package */ interface IInspectorPackagerConnection { + public void connect(); + + public void closeQuietly(); + + public void sendEventToAllConnections(String event); +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java index 298fc5ce176..84107fb5e4c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java @@ -27,7 +27,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -public class InspectorPackagerConnection { +public class InspectorPackagerConnection implements IInspectorPackagerConnection { private static final String TAG = "InspectorPackagerConnection"; private final Connection mConnection; diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index feb7a3b33ca..0736ec47faf 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -125,6 +125,7 @@ add_react_android_subdir(src/main/jni/react/runtime/cxxreactpackage) add_react_android_subdir(src/main/jni/react/runtime/jni) add_react_android_subdir(src/main/jni/react/runtime/hermes/jni) add_react_android_subdir(src/main/jni/react/runtime/jsc/jni) +add_react_android_subdir(src/main/jni/react/devsupport) # GTest dependencies add_executable(reactnative_unittest diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt new file mode 100644 index 00000000000..c2c6b29a744 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt @@ -0,0 +1,19 @@ +# 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. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +add_compile_options(-fexceptions -frtti -std=c++20 -Wall -DLOG_TAG=\"ReactNative\") + +file(GLOB react_devsupportjni_SRC CONFIGURE_DEPENDS *.cpp) + +add_library(react_devsupportjni SHARED ${react_devsupportjni_SRC}) + +target_include_directories(react_devsupportjni PUBLIC .) + +target_link_libraries(react_devsupportjni + fbjni + jsinspector) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.cpp new file mode 100644 index 00000000000..ab900a18bd8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.cpp @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnection.h" + +#include + +using namespace facebook::jni; + +namespace facebook::react::jsinspector_modern { + +JCxxInspectorPackagerConnection::JCxxInspectorPackagerConnection( + const std::string& url, + const std::string& packageName, + alias_ref delegate) + : cxxImpl_(url, packageName, delegate->wrapInUniquePtr()) {} + +local_ref +JCxxInspectorPackagerConnection::initHybrid( + alias_ref, + const std::string& url, + const std::string& packageName, + alias_ref delegate) { + return makeCxxInstance(url, packageName, delegate); +} + +void JCxxInspectorPackagerConnection::connect() { + cxxImpl_.connect(); +} + +void JCxxInspectorPackagerConnection::closeQuietly() { + cxxImpl_.closeQuietly(); +} + +void JCxxInspectorPackagerConnection::sendEventToAllConnections( + const std::string& event) { + cxxImpl_.sendEventToAllConnections(event); +} + +void JCxxInspectorPackagerConnection::registerNatives() { + registerHybrid( + {makeNativeMethod( + "initHybrid", JCxxInspectorPackagerConnection::initHybrid), + makeNativeMethod("connect", JCxxInspectorPackagerConnection::connect), + makeNativeMethod( + "closeQuietly", JCxxInspectorPackagerConnection::closeQuietly), + makeNativeMethod( + "sendEventToAllConnections", + JCxxInspectorPackagerConnection::sendEventToAllConnections)}); +} +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.h new file mode 100644 index 00000000000..18252ae9d7a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#pragma once + +#include "JCxxInspectorPackagerConnectionDelegateImpl.h" + +#include +#include + +#include + +namespace facebook::react::jsinspector_modern { + +/** + * A hybrid Java/C++ class that exposes an instance of + * InspectorPackagerConnection to Java. + */ +class JCxxInspectorPackagerConnection + : public jni::HybridClass { + public: + constexpr static auto kJavaDescriptor = + "Lcom/facebook/react/devsupport/CxxInspectorPackagerConnection;"; + + static void registerNatives(); + + // InspectorPackagerConnection's public API + + void connect(); + void closeQuietly(); + void sendEventToAllConnections(const std::string& event); + + private: + friend HybridBase; + + using JDelegateImpl = JCxxInspectorPackagerConnectionDelegateImpl; + + /** + * Private constructor since this class can only be created from Java. + */ + JCxxInspectorPackagerConnection( + const std::string& url, + const std::string& packageName, + jni::alias_ref delegate); + + static jni::local_ref initHybrid( + jni::alias_ref, + const std::string& url, + const std::string& packageName, + jni::alias_ref delegate); + + /** + * The actual C++ implementation wrapped by this hybrid class. + */ + InspectorPackagerConnection cxxImpl_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.cpp new file mode 100644 index 00000000000..d10f8f295e5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.cpp @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnectionDelegateImpl.h" +#include "JCxxInspectorPackagerConnectionWebSocket.h" +#include "JCxxInspectorPackagerConnectionWebSocketDelegate.h" + +#include + +#include + +using namespace facebook::jni; + +namespace facebook::react::jsinspector_modern { + +std::unique_ptr +JCxxInspectorPackagerConnectionDelegateImpl::connectWebSocket( + const std::string& url, + std::weak_ptr delegate) { + using JWebSocket = JCxxInspectorPackagerConnectionWebSocket; + using JWebSocketDelegate = JCxxInspectorPackagerConnectionWebSocketDelegate; + static auto method = + javaClassStatic() + ->getMethod( + const std::string&, alias_ref)>( + "connectWebSocket"); + auto jWebSocket = method( + self(), url, make_global(JWebSocketDelegate::newObjectCxxArgs(delegate))); + return jWebSocket->wrapInUniquePtr(); +} + +void JCxxInspectorPackagerConnectionDelegateImpl::scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs) { + static auto method = + javaClassStatic() + ->getMethod, jlong)>( + "scheduleCallback"); + method( + self(), + JNativeRunnable::newObjectCxxArgs(std::move(callback)), + static_cast(delayMs.count())); +} + +std::unique_ptr +JCxxInspectorPackagerConnectionDelegateImpl::wrapInUniquePtr() { + return std::unique_ptr{ + new RefWrapper{self()}}; +} + +JCxxInspectorPackagerConnectionDelegateImpl::RefWrapper::RefWrapper( + jni::alias_ref jDelegate) + : jDelegate_(make_global(jDelegate)) {} + +std::unique_ptr +JCxxInspectorPackagerConnectionDelegateImpl::RefWrapper::connectWebSocket( + const std::string& url, + std::weak_ptr delegate) { + return jDelegate_->connectWebSocket(url, delegate); +} + +void JCxxInspectorPackagerConnectionDelegateImpl::RefWrapper::scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs) { + return jDelegate_->scheduleCallback(callback, delayMs); +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.h new file mode 100644 index 00000000000..087c5ecb579 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.h @@ -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. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace facebook::react::jsinspector_modern { + +/** + * Exposes an instance of the Java class + * CxxInspectorPackagerConnection.DelegateImpl to C++. The public interface + * mirrors InspectorPackagerConnectionDelegate exactly but doesn't inherit from + * it; this is because of fbjni limitations on multiple inheritance. To get a + * usable InspectorPackagerConnectionDelegate instance from this object, call + * wrapInUniquePtr(). + */ +struct JCxxInspectorPackagerConnectionDelegateImpl + : public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/devsupport/CxxInspectorPackagerConnection$DelegateImpl;"; + + std::unique_ptr wrapInUniquePtr(); + + // InspectorPackagerConnectionDelegate methods (mirrored) + + std::unique_ptr connectWebSocket( + const std::string& url, + std::weak_ptr delegate); + + void scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs); + + private: + class RefWrapper; +}; + +class JCxxInspectorPackagerConnectionDelegateImpl::RefWrapper + : public InspectorPackagerConnectionDelegate { + public: + explicit RefWrapper(jni::alias_ref jDelegate); + + // InspectorPackagerConnectionDelegate methods + + 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; + + private: + jni::global_ref jDelegate_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.cpp new file mode 100644 index 00000000000..7de47273630 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.cpp @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnectionWebSocket.h" + +using namespace facebook::jni; +using namespace facebook::react::jsinspector_modern; + +namespace facebook::react::jsinspector_modern { + +void JCxxInspectorPackagerConnectionWebSocket::send(std::string_view message) { + static auto method = + javaClassStatic()->getMethod("send"); + method(self(), std::string(message)); +} + +void JCxxInspectorPackagerConnectionWebSocket::close() { + static auto method = javaClassStatic()->getMethod("close"); + method(self()); +} + +JCxxInspectorPackagerConnectionWebSocket:: + ~JCxxInspectorPackagerConnectionWebSocket() { + close(); +} + +std::unique_ptr +JCxxInspectorPackagerConnectionWebSocket::wrapInUniquePtr() { + return std::make_unique(self()); +} + +JCxxInspectorPackagerConnectionWebSocket::RefWrapper::RefWrapper( + alias_ref jWebSocket) + : jWebSocket_{make_global(jWebSocket)} {} + +void JCxxInspectorPackagerConnectionWebSocket::RefWrapper::send( + std::string_view message) { + jWebSocket_->send(message); +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.h new file mode 100644 index 00000000000..d73b360fb8a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include + +namespace facebook::react::jsinspector_modern { + +/** + * Exposes an implementation of the Java interface + * CxxInspectorPackagerConnection.IWebSocket to C++. The public interface + * mirrors IWebSocket exactly but doesn't inherit from + * it; this is because of fbjni limitations on multiple inheritance. To get a + * usable IWebSocket instance from this object, call + * wrapInUniquePtr(). + */ +class JCxxInspectorPackagerConnectionWebSocket + : public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/devsupport/CxxInspectorPackagerConnection$IWebSocket;"; + + ~JCxxInspectorPackagerConnectionWebSocket(); + + std::unique_ptr wrapInUniquePtr(); + + // IWebSocket methods (mirrored) + void send(std::string_view message); + + private: + class RefWrapper; + + void close(); +}; + +class JCxxInspectorPackagerConnectionWebSocket::RefWrapper : public IWebSocket { + public: + explicit RefWrapper(jni::alias_ref jWebSocket_); + + // IWebSocket methods + + virtual void send(std::string_view message) override; + + private: + jni::global_ref jWebSocket_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.cpp new file mode 100644 index 00000000000..353ea02312d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.cpp @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnectionWebSocketDelegate.h" + +using namespace facebook::jni; + +namespace facebook::react::jsinspector_modern { + +JCxxInspectorPackagerConnectionWebSocketDelegate:: + JCxxInspectorPackagerConnectionWebSocketDelegate( + std::weak_ptr cxxDelegate) + : cxxDelegate_(cxxDelegate) {} + +void JCxxInspectorPackagerConnectionWebSocketDelegate::didFailWithError( + alias_ref posixCode, + const std::string& error) { + if (auto delegate = cxxDelegate_.lock()) { + delegate->didFailWithError(*posixCode, error); + } +} + +void JCxxInspectorPackagerConnectionWebSocketDelegate::didReceiveMessage( + const std::string& message) { + if (auto delegate = cxxDelegate_.lock()) { + delegate->didReceiveMessage(message); + } +} + +void JCxxInspectorPackagerConnectionWebSocketDelegate::didClose() { + if (auto delegate = cxxDelegate_.lock()) { + delegate->didClose(); + } +} + +void JCxxInspectorPackagerConnectionWebSocketDelegate::registerNatives() { + registerHybrid( + {makeNativeMethod( + "didFailWithError", + JCxxInspectorPackagerConnectionWebSocketDelegate::didFailWithError), + makeNativeMethod( + "didReceiveMessage", + JCxxInspectorPackagerConnectionWebSocketDelegate::didReceiveMessage), + makeNativeMethod( + "didClose", + JCxxInspectorPackagerConnectionWebSocketDelegate::didClose)}); +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.h new file mode 100644 index 00000000000..d822e51e64f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include "JOptional.h" + +#include +#include + +#include +#include + +namespace facebook::react::jsinspector_modern { + +/** + * A hybrid Java/C++ class that exposes a C++ implementation of + * IWebSocketDelegate to Java. + */ +class JCxxInspectorPackagerConnectionWebSocketDelegate + : public jni::HybridClass< + JCxxInspectorPackagerConnectionWebSocketDelegate> { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/devsupport/CxxInspectorPackagerConnection$WebSocketDelegate;"; + + void didFailWithError( + jni::alias_ref posixCode, + const std::string& error); + + void didReceiveMessage(const std::string& message); + + void didClose(); + + static void registerNatives(); + + JCxxInspectorPackagerConnectionWebSocketDelegate( + std::weak_ptr cxxDelegate); + + private: + friend HybridBase; + std::weak_ptr cxxDelegate_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.cpp new file mode 100644 index 00000000000..17eb2ddac84 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.cpp @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#include "JOptional.h" + +namespace facebook::react { + +int JOptionalInt::getAsInt() const { + static auto method = javaClassStatic()->getMethod("getAsInt"); + return method(self()); +} + +bool JOptionalInt::isPresent() const { + static auto method = javaClassStatic()->getMethod("isPresent"); + return method(self()); +} + +JOptionalInt::operator std::optional() const { + if (!isPresent()) { + return {}; + } + return getAsInt(); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.h new file mode 100644 index 00000000000..7338df4b138 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +// TODO(moti): Consider moving this into fbjni + +namespace facebook::react { + +class JOptionalInt : public facebook::jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/util/OptionalInt;"; + + int getAsInt() const; + bool isPresent() const; + operator std::optional() const; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/OnLoad.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/OnLoad.cpp new file mode 100644 index 00000000000..b9f61a958dd --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/OnLoad.cpp @@ -0,0 +1,20 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnection.h" +#include "JCxxInspectorPackagerConnectionWebSocketDelegate.h" + +#include + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + return facebook::jni::initialize(vm, [] { + facebook::react::jsinspector_modern::JCxxInspectorPackagerConnection:: + registerNatives(); + facebook::react::jsinspector_modern:: + JCxxInspectorPackagerConnectionWebSocketDelegate::registerNatives(); + }); +} diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.cpp index bbb92abaaf6..607aab0ac23 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.cpp @@ -16,11 +16,20 @@ bool JInspectorFlags::getEnableModernCDPRegistry(jni::alias_ref) { return inspectorFlags.getEnableModernCDPRegistry(); } +bool JInspectorFlags::getEnableCxxInspectorPackagerConnection( + jni::alias_ref) { + auto& inspectorFlags = jsinspector_modern::InspectorFlags::getInstance(); + return inspectorFlags.getEnableCxxInspectorPackagerConnection(); +} + void JInspectorFlags::registerNatives() { javaClassLocal()->registerNatives({ makeNativeMethod( "getEnableModernCDPRegistry", JInspectorFlags::getEnableModernCDPRegistry), + makeNativeMethod( + "getEnableCxxInspectorPackagerConnection", + JInspectorFlags::getEnableCxxInspectorPackagerConnection), }); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.h index 5e2cc2f70b2..7944f836036 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.h @@ -21,6 +21,8 @@ class JInspectorFlags : public jni::JavaClass { static bool getEnableModernCDPRegistry(jni::alias_ref); + static bool getEnableCxxInspectorPackagerConnection(jni::alias_ref); + static void registerNatives(); private: