/* * 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 "HostTarget.h" #include "CdpJson.h" #include "HostAgent.h" #include "InspectorInterfaces.h" #include "InspectorUtilities.h" #include "InstanceTarget.h" #include "SessionState.h" #include #include #include namespace facebook::react::jsinspector_modern { /** * A Session connected to a HostTarget, passing CDP messages to and from a * HostAgent which it owns. */ class HostTargetSession { public: explicit HostTargetSession( std::unique_ptr remote, HostTargetController& targetController, HostTargetMetadata hostMetadata, VoidExecutor executor) : remote_(std::make_shared(std::move(remote))), frontendChannel_( [remoteWeak = std::weak_ptr(remote_)](std::string_view message) { if (auto remote = remoteWeak.lock()) { remote->onMessage(std::string(message)); } }), hostAgent_( frontendChannel_, targetController, std::move(hostMetadata), state_, executor) {} /** * Called by CallbackLocalConnection to send a message to this Session's * Agent. */ void operator()(std::string message) { cdp::PreparsedRequest request; // Messages may be invalid JSON, or have unexpected types. try { request = cdp::preparse(message); } catch (const cdp::ParseError& e) { frontendChannel_( cdp::jsonError(std::nullopt, cdp::ErrorCode::ParseError, e.what())); return; } catch (const cdp::TypeError& e) { frontendChannel_(cdp::jsonError( std::nullopt, cdp::ErrorCode::InvalidRequest, e.what())); return; } try { hostAgent_.handleRequest(request); } // Catch exceptions that may arise from accessing dynamic params during // request handling. catch (const cdp::TypeError& e) { frontendChannel_( cdp::jsonError(request.id, cdp::ErrorCode::InvalidRequest, e.what())); return; } // Catch exceptions for unrecognised or partially implemented CDP methods. catch (const NotImplementedException& e) { frontendChannel_( cdp::jsonError(request.id, cdp::ErrorCode::MethodNotFound, e.what())); return; } } /** * Replace the current instance agent inside hostAgent_ with a new one * connected to the new InstanceTarget. * \param instance The new instance target. May be nullptr to indicate * there's no current instance. */ void setCurrentInstance(InstanceTarget* instance) { if (instance) { hostAgent_.setCurrentInstanceAgent( instance->createAgent(frontendChannel_, state_)); } else { hostAgent_.setCurrentInstanceAgent(nullptr); } } private: // Owned by this instance, but shared (weakly) with the frontend channel std::shared_ptr remote_; FrontendChannel frontendChannel_; SessionState state_; // NOTE: hostAgent_ has a raw reference to state_ so must be destroyed first. HostAgent hostAgent_; }; /** * Converts HostCommands to CDP method calls and sends them over a private * connection to the HostTarget. */ class HostCommandSender { public: explicit HostCommandSender(HostTarget& target) : connection_(target.connect(std::make_unique())) {} /** * Send a \c HostCommand to the HostTarget. */ void sendCommand(HostCommand command) { cdp::RequestId id = makeRequestId(); switch (command) { case HostCommand::DebuggerResume: connection_->sendMessage(cdp::jsonRequest(id, "Debugger.resume")); break; case HostCommand::DebuggerStepOver: connection_->sendMessage(cdp::jsonRequest(id, "Debugger.stepOver")); break; default: assert(false && "unknown HostCommand"); } } private: cdp::RequestId makeRequestId() { return nextRequestId_++; } cdp::RequestId nextRequestId_{1}; std::unique_ptr connection_; }; std::shared_ptr HostTarget::create( HostTargetDelegate& delegate, VoidExecutor executor) { std::shared_ptr hostTarget{new HostTarget(delegate)}; hostTarget->setExecutor(executor); return hostTarget; } HostTarget::HostTarget(HostTargetDelegate& delegate) : delegate_(delegate), executionContextManager_{std::make_shared()} {} std::unique_ptr HostTarget::connect( std::unique_ptr connectionToFrontend) { auto session = std::make_shared( std::move(connectionToFrontend), controller_, delegate_.getMetadata(), makeVoidExecutor(executorFromThis())); session->setCurrentInstance(currentInstance_.get()); sessions_.insert(std::weak_ptr(session)); return std::make_unique( [session](std::string message) { (*session)(message); }); } HostTarget::~HostTarget() { // HostCommandSender owns a session, so we must release it for the assertion // below to be valid. commandSender_.reset(); // Sessions are owned by InspectorPackagerConnection, not by HostTarget, but // they hold a HostTarget& that we must guarantee is valid. assert( sessions_.empty() && "HostTargetSession objects must be destroyed before their HostTarget. Did you call getInspectorInstance().removePage()?"); } HostTargetDelegate::~HostTargetDelegate() {} InstanceTarget& HostTarget::registerInstance(InstanceTargetDelegate& delegate) { assert(!currentInstance_ && "Only one instance allowed"); currentInstance_ = InstanceTarget::create( executionContextManager_, delegate, makeVoidExecutor(executorFromThis())); sessions_.forEach( [currentInstance = &*currentInstance_](HostTargetSession& session) { session.setCurrentInstance(currentInstance); }); return *currentInstance_; } void HostTarget::unregisterInstance(InstanceTarget& instance) { assert( currentInstance_ && currentInstance_.get() == &instance && "Invalid unregistration"); sessions_.forEach( [](HostTargetSession& session) { session.setCurrentInstance(nullptr); }); currentInstance_.reset(); } void HostTarget::sendCommand(HostCommand command) { executorFromThis()([command](HostTarget& self) { if (!self.commandSender_) { self.commandSender_ = std::make_unique(self); } self.commandSender_->sendCommand(command); }); } HostTargetController::HostTargetController(HostTarget& target) : target_(target) {} HostTargetDelegate& HostTargetController::getDelegate() { return target_.getDelegate(); } bool HostTargetController::hasInstance() const { return target_.hasInstance(); } void HostTargetController::incrementPauseOverlayCounter() { ++pauseOverlayCounter_; } bool HostTargetController::decrementPauseOverlayCounter() { assert(pauseOverlayCounter_ > 0 && "Pause overlay counter underflow"); if (--pauseOverlayCounter_ == 0) { return false; } return true; } folly::dynamic hostMetadataToDynamic(const HostTargetMetadata& metadata) { folly::dynamic result = folly::dynamic::object; if (metadata.appDisplayName) { result["appDisplayName"] = metadata.appDisplayName.value(); } if (metadata.appIdentifier) { result["appIdentifier"] = metadata.appIdentifier.value(); } if (metadata.deviceName) { result["deviceName"] = metadata.deviceName.value(); } if (metadata.integrationName) { result["integrationName"] = metadata.integrationName.value(); } if (metadata.platform) { result["platform"] = metadata.platform.value(); } if (metadata.reactNativeVersion) { result["reactNativeVersion"] = metadata.reactNativeVersion.value(); } return result; } } // namespace facebook::react::jsinspector_modern