/* * 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 #ifdef FLIPPER_OSS #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace facebook; using namespace facebook::flipper; namespace { void handleException(const std::exception& e) { std::string message = "Exception caught in C++ and suppressed: "; message += e.what(); __android_log_write(ANDROID_LOG_ERROR, "FLIPPER", message.c_str()); } class JEventBase : public jni::HybridClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/android/EventBase;"; static void registerNatives() { registerHybrid({ makeNativeMethod("initHybrid", JEventBase::initHybrid), makeNativeMethod("loopForever", JEventBase::loopForever), }); } folly::EventBase* eventBase() { return &eventBase_; } private: friend HybridBase; JEventBase() {} void loopForever() { folly::EventBaseManager::get()->setEventBase(&eventBase_, false); eventBase_.loopForever(); } static void initHybrid(jni::alias_ref o) { return setCxxInstance(o); } folly::EventBase eventBase_; }; class JFlipperObject : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/core/FlipperObject;"; static jni::local_ref create(const folly::dynamic& json) { return newInstance(folly::toJson(json)); } std::string toJsonString() { static const auto method = javaClassStatic()->getMethod("toJsonString"); return method(self())->toStdString(); } }; class JFlipperArray : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/core/FlipperArray;"; static jni::local_ref create(const folly::dynamic& json) { return newInstance(folly::toJson(json)); } std::string toJsonString() { static const auto method = javaClassStatic()->getMethod("toJsonString"); return method(self())->toStdString(); } }; class JFlipperSocketEventHandler : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/core/FlipperSocketEventHandler;"; }; class JFlipperWebSocket; class JFlipperSocketEventHandlerImpl : public jni::HybridClass< JFlipperSocketEventHandlerImpl, JFlipperSocketEventHandler> { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/android/FlipperSocketEventHandlerImpl;"; static void registerNatives() { registerHybrid({ makeNativeMethod( "reportConnectionEvent", JFlipperSocketEventHandlerImpl::reportConnectionEvent), makeNativeMethod( "reportMessageReceived", JFlipperSocketEventHandlerImpl::reportMessageReceived), makeNativeMethod( "reportAuthenticationChallengeReceived", JFlipperSocketEventHandlerImpl:: reportAuthenticationChallengeReceived), }); } void reportConnectionEvent(int code) { _eventHandler((SocketEvent)code); } void reportMessageReceived(const std::string& message) { _messageHandler(message); } jni::global_ref reportAuthenticationChallengeReceived() { auto object = _certificateProvider(); return make_global(object); } private: friend HybridBase; SocketEventHandler _eventHandler; SocketMessageHandler _messageHandler; using CustomProvider = std::function()>; CustomProvider _certificateProvider; JFlipperSocketEventHandlerImpl( SocketEventHandler eventHandler, SocketMessageHandler messageHandler, CustomProvider certificateProvider) : _eventHandler(std::move(eventHandler)), _messageHandler(std::move(messageHandler)), _certificateProvider(std::move(certificateProvider)) {} }; class JFlipperSocket : public jni::JavaClass {}; class JFlipperSocketImpl : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/android/FlipperSocketImpl;"; static jni::local_ref create(const std::string& url) { return newInstance(url); } void connect() { static const auto method = getClass()->getMethod("flipperConnect"); try { method(self()); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void disconnect() { static const auto method = getClass()->getMethod("flipperDisconnect"); try { method(self()); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void send(const std::string& message) { static const auto method = getClass()->getMethod("flipperSend"); try { method(self(), message); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void setEventHandler( jni::alias_ref eventHandler) { static const auto method = getClass()->getMethod)>( "flipperSetEventHandler"); try { method(self(), eventHandler); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } }; class JFlipperWebSocket : public facebook::flipper::FlipperSocket { public: JFlipperWebSocket( facebook::flipper::FlipperConnectionEndpoint endpoint, std::unique_ptr payload) : endpoint_(std::move(endpoint)), payload_(std::move(payload)) {} JFlipperWebSocket( facebook::flipper::FlipperConnectionEndpoint endpoint, std::unique_ptr payload, facebook::flipper::ConnectionContextStore* connectionContextStore) : endpoint_(std::move(endpoint)), payload_(std::move(payload)), connectionContextStore_(connectionContextStore) {} virtual ~JFlipperWebSocket() { if (socket_ != nullptr) { socket_->disconnect(); socket_ = nullptr; } } virtual void setEventHandler(SocketEventHandler eventHandler) override { eventHandler_ = std::move(eventHandler); } virtual void setMessageHandler(SocketMessageHandler messageHandler) override { messageHandler_ = std::move(messageHandler); } virtual bool connect(FlipperConnectionManager* manager) override { if (socket_ != nullptr) { return true; } std::string connectionURL = endpoint_.secure ? "wss://" : "ws://"; connectionURL += endpoint_.host; connectionURL += ":"; connectionURL += std::to_string(endpoint_.port); auto serializer = facebook::flipper::URLSerializer{}; payload_->serialize(serializer); auto payload = serializer.serialize(); if (payload.size()) { connectionURL += "?"; connectionURL += payload; } auto secure = endpoint_.secure; std::promise promise; auto connected = promise.get_future(); connecting_ = true; socket_ = make_global(JFlipperSocketImpl::create(connectionURL)); socket_->setEventHandler(JFlipperSocketEventHandlerImpl::newObjectCxxArgs( [this, &promise, eventHandler = eventHandler_](SocketEvent event) { /** Only fulfill the promise the first time the event handler is used. If the open event is received, then set the promise value to true. For any other event, consider a failure and set to false. */ if (this->connecting_) { this->connecting_ = false; if (event == SocketEvent::OPEN) { promise.set_value(true); } else if (event == SocketEvent::SSL_ERROR) { try { promise.set_exception( std::make_exception_ptr(folly::AsyncSocketException( folly::AsyncSocketException::SSL_ERROR, "SSL handshake failed"))); } catch (...) { // set_exception() may throw an exception // In that case, just set the value to false. promise.set_value(false); } } else { promise.set_value(false); } } eventHandler(event); }, [messageHandler = messageHandler_](const std::string& message) { messageHandler(message); }, [secure, store = connectionContextStore_]() { folly::dynamic object_ = folly::dynamic::object(); if (secure) { auto certificate = store->getCertificate(); if (certificate.first.length() == 0) { return JFlipperObject::create(nullptr); } object_["certificates_client_path"] = certificate.first; object_["certificates_client_pass"] = certificate.second; object_["certificates_ca_path"] = store->getCACertificatePath(); } return JFlipperObject::create(std::move(object_)); })); socket_->connect(); auto state = connected.wait_for(std::chrono::seconds(10)); if (state == std::future_status::ready) { return connected.get(); } disconnect(); return false; } virtual void disconnect() override { if (socket_ == nullptr) { return; } socket_->disconnect(); socket_ = nullptr; } virtual void send(const folly::dynamic& message, SocketSendHandler completion) override { if (socket_ == nullptr) { return; } std::string json = folly::toJson(message); send(json, std::move(completion)); } virtual void send(const std::string& message, SocketSendHandler completion) override { if (socket_ == nullptr) { return; } socket_->send(message); completion(); } virtual void sendExpectResponse( const std::string& message, SocketSendExpectResponseHandler completion) override { if (socket_ == nullptr) { return; } socket_->setEventHandler(JFlipperSocketEventHandlerImpl::newObjectCxxArgs( [eventHandler = eventHandler_](SocketEvent event) { eventHandler(event); }, [completion, message](const std::string& msg) { completion(msg, false); }, []() { folly::dynamic object_ = folly::dynamic::object(); return JFlipperObject::create(std::move(object_)); })); socket_->send(message); } private: facebook::flipper::FlipperConnectionEndpoint endpoint_; std::unique_ptr payload_; facebook::flipper::ConnectionContextStore* connectionContextStore_; facebook::flipper::SocketEventHandler eventHandler_; facebook::flipper::SocketMessageHandler messageHandler_; jni::global_ref socket_; bool connecting_; }; class JFlipperSocketProvider : public facebook::flipper::FlipperSocketProvider { public: JFlipperSocketProvider() {} virtual std::unique_ptr create( facebook::flipper::FlipperConnectionEndpoint endpoint, std::unique_ptr payload, folly::EventBase* eventBase) override { return std::make_unique( std::move(endpoint), std::move(payload)); ; } virtual std::unique_ptr create( FlipperConnectionEndpoint endpoint, std::unique_ptr payload, folly::EventBase* eventBase, ConnectionContextStore* connectionContextStore) override { return std::make_unique( std::move(endpoint), std::move(payload), connectionContextStore); } }; class JFlipperResponder : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/core/FlipperResponder;"; }; class JFlipperResponderImpl : public jni::HybridClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/android/FlipperResponderImpl;"; static void registerNatives() { registerHybrid({ makeNativeMethod("successObject", JFlipperResponderImpl::successObject), makeNativeMethod("successArray", JFlipperResponderImpl::successArray), makeNativeMethod("error", JFlipperResponderImpl::error), }); } void successObject(jni::alias_ref json) { _responder->success( json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); } void successArray(jni::alias_ref json) { _responder->success( json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); } void error(jni::alias_ref json) { _responder->error( json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); } private: friend HybridBase; std::shared_ptr _responder; JFlipperResponderImpl(std::shared_ptr responder) : _responder(std::move(responder)) {} }; class JFlipperReceiver : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/core/FlipperReceiver;"; void receive( const folly::dynamic params, std::shared_ptr responder) const { static const auto method = javaClassStatic() ->getMethod, jni::alias_ref)>("onReceive"); method( self(), JFlipperObject::create(std::move(params)), JFlipperResponderImpl::newObjectCxxArgs(responder)); } }; class JFlipperConnection : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/core/FlipperConnection;"; }; class JFlipperConnectionImpl : public jni::HybridClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/android/FlipperConnectionImpl;"; static void registerNatives() { registerHybrid({ makeNativeMethod("sendObject", JFlipperConnectionImpl::sendObject), makeNativeMethod("sendArray", JFlipperConnectionImpl::sendArray), makeNativeMethod("reportError", JFlipperConnectionImpl::reportError), makeNativeMethod( "reportErrorWithMetadata", JFlipperConnectionImpl::reportErrorWithMetadata), makeNativeMethod("receive", JFlipperConnectionImpl::receive), }); } void sendObject( const std::string method, jni::alias_ref json) { _connection->send( std::move(method), json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); } void sendArray(const std::string method, jni::alias_ref json) { _connection->send( std::move(method), json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); } void reportErrorWithMetadata( const std::string reason, const std::string stackTrace) { _connection->error(reason, stackTrace); } void reportError(jni::alias_ref throwable) { _connection->error( throwable->toString(), throwable->getStackTrace()->toString()); } void receive( const std::string method, jni::alias_ref receiver) { auto global = make_global(receiver); _connection->receive( std::move(method), [global]( const folly::dynamic& params, std::shared_ptr responder) { global->receive(params, responder); }); } private: friend HybridBase; std::shared_ptr _connection; JFlipperConnectionImpl(std::shared_ptr connection) : _connection(std::move(connection)) {} }; class JFlipperPlugin : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/core/FlipperPlugin;"; std::string identifier() const { static const auto method = javaClassStatic()->getMethod("getId"); try { return method(self())->toStdString(); } catch (const std::exception& e) { handleException(e); return ""; } catch (const std::exception* e) { if (e) { handleException(*e); } return ""; } } void didConnect(std::shared_ptr conn) { auto method = javaClassStatic() ->getMethod)>( "onConnect"); try { method(self(), JFlipperConnectionImpl::newObjectCxxArgs(conn)); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void didDisconnect() { static const auto method = javaClassStatic()->getMethod("onDisconnect"); try { method(self()); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } bool runInBackground() { static const auto method = javaClassStatic()->getMethod("runInBackground"); try { return method(self()) == JNI_TRUE; } catch (const std::exception& e) { handleException(e); return false; } catch (const std::exception* e) { if (e) { handleException(*e); } return false; } } }; class JFlipperStateUpdateListener : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/core/FlipperStateUpdateListener;"; void onUpdate() { try { static const auto method = javaClassStatic()->getMethod("onUpdate"); method(self()); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void onStepStarted(std::string step) { try { static const auto method = javaClassStatic()->getMethod("onStepStarted"); method(self(), step); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void onStepSuccess(std::string step) { try { static const auto method = javaClassStatic()->getMethod("onStepSuccess"); method(self(), step); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void onStepFailed(std::string step, std::string errorMessage) { try { static const auto method = javaClassStatic()->getMethod( "onStepFailed"); method(self(), step, errorMessage); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } }; class AndroidFlipperStateUpdateListener : public FlipperStateUpdateListener { public: AndroidFlipperStateUpdateListener( jni::alias_ref stateListener); void onUpdate(); private: jni::global_ref jStateListener; }; class JFlipperPluginWrapper : public FlipperPlugin { public: jni::global_ref jplugin; virtual std::string identifier() const override { return jplugin->identifier(); } virtual void didConnect(std::shared_ptr conn) override { jplugin->didConnect(conn); } virtual void didDisconnect() override { jplugin->didDisconnect(); } virtual bool runInBackground() override { return jplugin->runInBackground(); } JFlipperPluginWrapper(jni::global_ref plugin) : jplugin(plugin) {} }; struct JStateSummary : public jni::JavaClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/core/StateSummary;"; static jni::local_ref create() { return newInstance(); } void addEntry(std::string name, std::string state) { static const auto method = javaClassStatic()->getMethod( "addEntry"); return method(self(), name, state); } }; class JFlipperClient : public jni::HybridClass { public: constexpr static auto kJavaDescriptor = "Lcom/facebook/flipper/android/FlipperClientImpl;"; static void registerNatives() { registerHybrid({ makeNativeMethod("init", JFlipperClient::init), makeNativeMethod("getInstance", JFlipperClient::getInstance), makeNativeMethod("start", JFlipperClient::start), makeNativeMethod("stop", JFlipperClient::stop), makeNativeMethod("addPluginNative", JFlipperClient::addPlugin), makeNativeMethod("removePluginNative", JFlipperClient::removePlugin), makeNativeMethod( "subscribeForUpdates", JFlipperClient::subscribeForUpdates), makeNativeMethod("unsubscribe", JFlipperClient::unsubscribe), makeNativeMethod("getPlugin", JFlipperClient::getPlugin), makeNativeMethod("getState", JFlipperClient::getState), makeNativeMethod("getStateSummary", JFlipperClient::getStateSummary), }); } static jni::alias_ref getInstance( jni::alias_ref) { try { static auto client = make_global(newObjectCxxArgs()); return client; } catch (const std::exception& e) { return nullptr; } } void start() { try { FlipperClient::instance()->start(); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void stop() { try { FlipperClient::instance()->stop(); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void addPlugin(jni::alias_ref plugin) { try { auto wrapper = std::make_shared(make_global(plugin)); FlipperClient::instance()->addPlugin(wrapper); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void removePlugin(jni::alias_ref plugin) { try { auto client = FlipperClient::instance(); client->removePlugin(client->getPlugin(plugin->identifier())); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void subscribeForUpdates( jni::alias_ref stateListener) { try { auto client = FlipperClient::instance(); mStateListener = std::make_shared(stateListener); client->setStateListener(mStateListener); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } void unsubscribe() { try { auto client = FlipperClient::instance(); mStateListener = nullptr; client->setStateListener(nullptr); } catch (const std::exception& e) { handleException(e); } catch (const std::exception* e) { if (e) { handleException(*e); } } } std::string getState() { try { return FlipperClient::instance()->getState(); } catch (const std::exception& e) { handleException(e); return ""; } catch (const std::exception* e) { if (e) { handleException(*e); } return ""; } } jni::global_ref getStateSummary() { try { auto summary = jni::make_global(JStateSummary::create()); auto elements = FlipperClient::instance()->getStateElements(); for (auto&& element : elements) { std::string status; switch (element.state_) { case State::in_progress: status = "IN_PROGRESS"; break; case State::failed: status = "FAILED"; break; case State::success: status = "SUCCESS"; break; } summary->addEntry(element.name_, status); } return summary; } catch (const std::exception& e) { handleException(e); return nullptr; } catch (const std::exception* e) { if (e) { handleException(*e); } return nullptr; } } jni::alias_ref getPlugin(const std::string& identifier) { try { auto plugin = FlipperClient::instance()->getPlugin(identifier); if (plugin) { auto wrapper = std::static_pointer_cast(plugin); return wrapper->jplugin; } else { return nullptr; } } catch (const std::exception& e) { handleException(e); return nullptr; } catch (const std::exception* e) { if (e) { handleException(*e); } return nullptr; } } static void init( jni::alias_ref, JEventBase* callbackWorker, JEventBase* connectionWorker, int insecurePort, int securePort, int altInsecurePort, int altSecurePort, const std::string host, const std::string os, const std::string device, const std::string deviceId, const std::string app, const std::string appId, const std::string privateAppDirectory) { FlipperClient::init( {{std::move(host), std::move(os), std::move(device), std::move(deviceId), std::move(app), std::move(appId), std::move(privateAppDirectory)}, callbackWorker->eventBase(), connectionWorker->eventBase(), insecurePort, securePort, altInsecurePort, altSecurePort}); facebook::flipper::FlipperSocketProvider::setDefaultProvider( std::make_unique()); } private: friend HybridBase; std::shared_ptr mStateListener = nullptr; JFlipperClient() {} }; } // namespace jint JNI_OnLoad(JavaVM* vm, void*) { return jni::initialize(vm, [] { JFlipperClient::registerNatives(); JFlipperConnectionImpl::registerNatives(); JFlipperResponderImpl::registerNatives(); JEventBase::registerNatives(); JFlipperSocketEventHandlerImpl::registerNatives(); }); } AndroidFlipperStateUpdateListener::AndroidFlipperStateUpdateListener( jni::alias_ref stateListener) { jStateListener = jni::make_global(stateListener); } void AndroidFlipperStateUpdateListener::onUpdate() { jStateListener->onUpdate(); }