react-native/ReactCommon/inspector/InspectorController.cpp

193 lines
5.4 KiB
C++

// Copyright 2004-present Facebook. All Rights Reserved.
#include "InspectorController.h"
#include "Error.h"
#include "Agent.h"
#include "LegacyInspectorEnvironment.h"
#include "InspectorAgent.h"
#include "PageAgent.h"
#include "ConsoleAgent.h"
#include "JSDispatcher.h"
#include "LegacyAgents.h"
#include <folly/Memory.h>
#include <folly/Conv.h>
#include <folly/json.h>
namespace facebook {
namespace react {
class ConcreteChannel : public Channel {
public:
ConcreteChannel(Receiver receiver)
: receiver_(std::move(receiver)) {}
void sendMessage(std::string message) override {
receiver_(std::move(message));
}
void registerDomain(std::string domain, MessageHandler handler) override {
domains_.emplace(std::move(domain), std::move(handler));
}
std::unordered_map<std::string, MessageHandler>& getDomains() {
return domains_;
}
private:
std::unordered_map<std::string, MessageHandler> domains_;
Receiver receiver_;
};
class MessageRouter {
public:
MessageRouter(ConcreteChannel* channel)
: channel_(channel) {
CHECK(channel_) << "Channel is null";
}
/*
* Messages are in JSON, formatted like:
* {
* "id": 1,
* "method": "Debugger.removeBreakpoint",
* "params": { "removeBreakpoint": "xyz" }
* }
*/
void route(std::string message) {
try {
auto json = parseJson(message);
auto callId = getCallId(json);
receive(callId, std::move(message), std::move(json));
} catch (const InspectorException& e) {
channel_->sendMessage(e.error());
}
}
private:
void receive(int callId, std::string message, folly::dynamic json) {
try {
auto method = Method::parse(json["method"].asString());
auto& handler = getHandler(method.domain());
handler(std::move(message), callId, method.name(), std::move(json["params"]));
} catch (const InspectorException& e) {
throw e.withCallId(callId);
} catch (const std::exception& e) {
LOG(ERROR) << "Dispatcher failed: " << e.what();
throw InspectorException(callId, ErrorCode::ServerError, "Internal error");
} catch (...) {
throw InspectorException(callId, ErrorCode::ServerError, "Internal error");
}
}
folly::dynamic parseJson(const std::string& message) {
try {
return folly::parseJson(message);
} catch (const std::runtime_error& e) {
throw InspectorException(ErrorCode::ParseError, "Message must be in JSON format");
}
}
int getCallId(folly::dynamic& json) {
auto& id = json["id"];
if (!id.isInt()) {
throw InspectorException(ErrorCode::InvalidRequest, "The type of 'id' property must be number");
} else {
return id.asInt();
}
}
Channel::MessageHandler& getHandler(const std::string& domain) {
try {
auto& domains = channel_->getDomains();
return domains.at(domain);
} catch (const std::out_of_range& e) {
throw InspectorException(ErrorCode::MethodNotFound, folly::to<std::string>("Unknown domain: '", domain, "'"));
}
}
ConcreteChannel* channel_;
};
class SchemaAgent : public Agent {
public:
SchemaAgent() {
registerMethod("getDomains", [this](folly::dynamic) -> folly::dynamic {
CHECK(channel_) << "Channel is null";
folly::dynamic names = folly::dynamic::array;
auto& domains = channel_->getDomains();
for (auto& entry : domains) {
// TODO(blom): Actually get version?
names.push_back(folly::dynamic::object("name", entry.first)("version", "1.0"));
}
return names;
});
}
void onConnect(std::shared_ptr<Channel> channel) override {
Agent::onConnect(channel);
channel_ = std::static_pointer_cast<ConcreteChannel>(channel);
}
private:
std::shared_ptr<ConcreteChannel> channel_;
std::string getDomain() override {
return "Schema";
}
};
InspectorController::InspectorController(JSC::JSGlobalObject& globalObject)
: globalObject_(globalObject) {
auto environment = folly::make_unique<LegacyInspectorEnvironment>();
auto inspectorAgent = folly::make_unique<InspectorAgent>();
inspectorAgent_ = inspectorAgent.get();
dispatchers_.push_back(std::move(inspectorAgent));
dispatchers_.push_back(folly::make_unique<SchemaAgent>());
dispatchers_.push_back(folly::make_unique<PageAgent>());
dispatchers_.push_back(folly::make_unique<JSDispatcher>(globalObject));
auto consoleAgent = folly::make_unique<ConsoleAgent>(globalObject, environment->injectedScriptManager());
auto legacyAgents = folly::make_unique<LegacyAgents>(globalObject, std::move(environment), consoleAgent.get());
dispatchers_.push_back(std::move(consoleAgent));
dispatchers_.push_back(std::move(legacyAgents));
}
InspectorController::~InspectorController() {
CHECK(!channel_) << "Wasn't disconnected";
}
void InspectorController::onConnect(Receiver receiver) {
CHECK(!channel_) << "Already connected";
channel_ = std::make_shared<ConcreteChannel>(std::move(receiver));
for (auto& dispatcher : dispatchers_) {
dispatcher->onConnect(channel_);
}
}
void InspectorController::onMessage(std::string message) {
CHECK(channel_) << "Not connected";
MessageRouter(channel_.get()).route(message);
}
void InspectorController::onGoingAway() {
CHECK(channel_) << "Not connected";
inspectorAgent_->detach();
}
void InspectorController::onDisconnect() {
CHECK(channel_) << "Not connected";
for (auto& dispatcher : dispatchers_) {
dispatcher->onDisconnect();
}
channel_.reset();
}
}
}