Summary: Flipper exposes a call() api to plugins which lets them call their sdk component, and it returns a promise with the response. Currently this is done by sending a fireAndForget request, noting the id of the request, and then receiving fireAndForget requests and matching up the ids to give the result back to the right plugin promise. Instead, it will be simpler to use rsocket requestResponse, instead of fireAndForget, which is for this exact use case. This diff adds a requestResponse handler to the SDK, so that it can deal with such requests and respond accordingly, while preserving the current functionality if it receives a fireAndForget. So this part is backwards compatible and should be safe to land in isolation. A later diff will change the desktop app to use requestResponse, which may not be backwards compatible, so that will have to be deployed more carefully. Reviewed By: passy Differential Revision: D13974049 fbshipit-source-id: b371d94a86b1f186375161ed8f2242a462ce418f
319 lines
9.5 KiB
C++
319 lines
9.5 KiB
C++
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the LICENSE
|
|
* file in the root directory of this source tree.
|
|
*/
|
|
#include "FlipperClient.h"
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
#include "ConnectionContextStore.h"
|
|
#include "FireAndForgetBasedFlipperResponder.h"
|
|
#include "FlipperConnectionImpl.h"
|
|
#include "FlipperConnectionManagerImpl.h"
|
|
#include "FlipperResponderImpl.h"
|
|
#include "FlipperState.h"
|
|
#include "FlipperStep.h"
|
|
#include "Log.h"
|
|
#if __ANDROID__
|
|
#include "utils/CallstackHelper.h"
|
|
#endif
|
|
#if __APPLE__
|
|
#include <execinfo.h>
|
|
#endif
|
|
|
|
#if FB_SONARKIT_ENABLED
|
|
|
|
namespace facebook {
|
|
namespace flipper {
|
|
|
|
static FlipperClient* kInstance;
|
|
|
|
using folly::dynamic;
|
|
|
|
void FlipperClient::init(FlipperInitConfig config) {
|
|
auto state = std::make_shared<FlipperState>();
|
|
auto context = std::make_shared<ConnectionContextStore>(config.deviceData);
|
|
kInstance = new FlipperClient(
|
|
std::make_unique<FlipperConnectionManagerImpl>(
|
|
std::move(config), state, context),
|
|
state);
|
|
}
|
|
|
|
FlipperClient* FlipperClient::instance() {
|
|
return kInstance;
|
|
}
|
|
|
|
void FlipperClient::setStateListener(
|
|
std::shared_ptr<FlipperStateUpdateListener> stateListener) {
|
|
performAndReportError([this, &stateListener]() {
|
|
log("Setting state listener");
|
|
flipperState_->setUpdateListener(stateListener);
|
|
});
|
|
}
|
|
|
|
void FlipperClient::addPlugin(std::shared_ptr<FlipperPlugin> plugin) {
|
|
performAndReportError([this, plugin]() {
|
|
log("FlipperClient::addPlugin " + plugin->identifier());
|
|
auto step = flipperState_->start("Add plugin " + plugin->identifier());
|
|
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (plugins_.find(plugin->identifier()) != plugins_.end()) {
|
|
throw std::out_of_range(
|
|
"plugin " + plugin->identifier() + " already added.");
|
|
}
|
|
plugins_[plugin->identifier()] = plugin;
|
|
step->complete();
|
|
if (connected_) {
|
|
refreshPlugins();
|
|
}
|
|
});
|
|
}
|
|
|
|
void FlipperClient::removePlugin(std::shared_ptr<FlipperPlugin> plugin) {
|
|
performAndReportError([this, plugin]() {
|
|
log("FlipperClient::removePlugin " + plugin->identifier());
|
|
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (plugins_.find(plugin->identifier()) == plugins_.end()) {
|
|
throw std::out_of_range("plugin " + plugin->identifier() + " not added.");
|
|
}
|
|
disconnect(plugin);
|
|
plugins_.erase(plugin->identifier());
|
|
if (connected_) {
|
|
refreshPlugins();
|
|
}
|
|
});
|
|
}
|
|
|
|
void FlipperClient::startBackgroundPlugins() {
|
|
std::cout << "Activating Background Plugins..." << std::endl;
|
|
for (std::map<std::string, std::shared_ptr<FlipperPlugin>>::iterator it =
|
|
plugins_.begin();
|
|
it != plugins_.end();
|
|
++it) {
|
|
std::cout << it->first << std::endl;
|
|
if (it->second.get()->runInBackground()) {
|
|
auto& conn = connections_[it->first];
|
|
conn = std::make_shared<FlipperConnectionImpl>(socket_.get(), it->first);
|
|
it->second.get()->didConnect(conn);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<FlipperPlugin> FlipperClient::getPlugin(
|
|
const std::string& identifier) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (plugins_.find(identifier) == plugins_.end()) {
|
|
return nullptr;
|
|
}
|
|
return plugins_.at(identifier);
|
|
}
|
|
|
|
bool FlipperClient::hasPlugin(const std::string& identifier) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
return plugins_.find(identifier) != plugins_.end();
|
|
}
|
|
|
|
void FlipperClient::disconnect(std::shared_ptr<FlipperPlugin> plugin) {
|
|
const auto& conn = connections_.find(plugin->identifier());
|
|
if (conn != connections_.end()) {
|
|
connections_.erase(plugin->identifier());
|
|
plugin->didDisconnect();
|
|
}
|
|
}
|
|
|
|
void FlipperClient::refreshPlugins() {
|
|
performAndReportError([this]() {
|
|
dynamic message = dynamic::object("method", "refreshPlugins");
|
|
socket_->sendMessage(message);
|
|
});
|
|
}
|
|
|
|
void FlipperClient::onConnected() {
|
|
performAndReportError([this]() {
|
|
log("FlipperClient::onConnected");
|
|
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
connected_ = true;
|
|
startBackgroundPlugins();
|
|
});
|
|
}
|
|
|
|
void FlipperClient::onDisconnected() {
|
|
performAndReportError([this]() {
|
|
log("FlipperClient::onDisconnected");
|
|
auto step = flipperState_->start("Trigger onDisconnected callbacks");
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
connected_ = false;
|
|
for (const auto& iter : plugins_) {
|
|
disconnect(iter.second);
|
|
}
|
|
step->complete();
|
|
});
|
|
}
|
|
|
|
void FlipperClient::onMessageReceived(
|
|
const dynamic& message,
|
|
std::unique_ptr<FlipperResponder> uniqueResponder) {
|
|
// Convert to shared pointer so we can hold on to it while passing it to the plugin, and still use it
|
|
// to respond with an error if we catch an exception.
|
|
std::shared_ptr<FlipperResponder> responder = std::move(uniqueResponder);
|
|
try {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
const auto& method = message["method"];
|
|
const auto& params = message.getDefault("params");
|
|
|
|
if (method == "getPlugins") {
|
|
dynamic identifiers = dynamic::array();
|
|
for (const auto& elem : plugins_) {
|
|
identifiers.push_back(elem.first);
|
|
}
|
|
dynamic response = dynamic::object("plugins", identifiers);
|
|
responder->success(response);
|
|
return;
|
|
}
|
|
|
|
if (method == "init") {
|
|
const auto identifier = params["plugin"].getString();
|
|
if (plugins_.find(identifier) == plugins_.end()) {
|
|
std::string errorMessage = "Plugin " + identifier +
|
|
" not found for method " + method.getString();
|
|
log(errorMessage);
|
|
responder->error(folly::dynamic::object("message", errorMessage)(
|
|
"name", "PluginNotFound"));
|
|
return;
|
|
}
|
|
const auto plugin = plugins_.at(identifier);
|
|
if (!plugin.get()->runInBackground()) {
|
|
auto& conn = connections_[plugin->identifier()];
|
|
conn = std::make_shared<FlipperConnectionImpl>(
|
|
socket_.get(), plugin->identifier());
|
|
plugin->didConnect(conn);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (method == "deinit") {
|
|
const auto identifier = params["plugin"].getString();
|
|
if (plugins_.find(identifier) == plugins_.end()) {
|
|
std::string errorMessage = "Plugin " + identifier +
|
|
" not found for method " + method.getString();
|
|
log(errorMessage);
|
|
responder->error(folly::dynamic::object("message", errorMessage)(
|
|
"name", "PluginNotFound"));
|
|
return;
|
|
}
|
|
const auto plugin = plugins_.at(identifier);
|
|
if (!plugin.get()->runInBackground()) {
|
|
disconnect(plugin);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (method == "execute") {
|
|
const auto identifier = params["api"].getString();
|
|
if (connections_.find(identifier) == connections_.end()) {
|
|
std::string errorMessage = "Connection " + identifier +
|
|
" not found for method " + method.getString();
|
|
log(errorMessage);
|
|
responder->error(folly::dynamic::object("message", errorMessage)(
|
|
"name", "ConnectionNotFound"));
|
|
return;
|
|
}
|
|
const auto& conn = connections_.at(params["api"].getString());
|
|
conn->call(
|
|
params["method"].getString(),
|
|
params.getDefault("params"),
|
|
responder);
|
|
return;
|
|
}
|
|
|
|
dynamic response =
|
|
dynamic::object("message", "Received unknown method: " + method);
|
|
responder->error(response);
|
|
} catch (std::exception& e) {
|
|
log(std::string("Error: ") + e.what());
|
|
if (responder) {
|
|
responder->error(dynamic::object("message", e.what())(
|
|
"stacktrace", callstack())("name", e.what()));
|
|
}
|
|
} catch (...) {
|
|
log("Unknown error suppressed in FlipperClient");
|
|
if (responder) {
|
|
responder->error(dynamic::object(
|
|
"message",
|
|
"Unknown error during " + message["method"] + ". " +
|
|
folly::toJson(message))("stacktrace", callstack())(
|
|
"name", "Unknown"));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string FlipperClient::callstack() {
|
|
#if __APPLE__
|
|
// For some iOS apps, __Unwind_Backtrace symbol wasn't found in sandcastle
|
|
// builds, thus, for iOS apps, using backtrace c function.
|
|
void* callstack[2048];
|
|
int frames = backtrace(callstack, 2048);
|
|
char** strs = backtrace_symbols(callstack, frames);
|
|
std::string output = "";
|
|
for (int i = 0; i < frames; ++i) {
|
|
output.append(strs[i]);
|
|
output.append("\n");
|
|
}
|
|
return output;
|
|
#elif __ANDROID__
|
|
const size_t max = 2048;
|
|
void* buffer[max];
|
|
std::ostringstream oss;
|
|
|
|
dumpBacktrace(oss, buffer, captureBacktrace(buffer, max));
|
|
std::string output = std::string(oss.str().c_str());
|
|
return output;
|
|
#else
|
|
return "";
|
|
#endif
|
|
}
|
|
void FlipperClient::performAndReportError(const std::function<void()>& func) {
|
|
#if FLIPPER_ENABLE_CRASH
|
|
// To debug the stack trace and an exception turn on the compiler flag
|
|
// FLIPPER_ENABLE_CRASH
|
|
func();
|
|
#else
|
|
try {
|
|
func();
|
|
} catch (std::exception& e) {
|
|
if (connected_) {
|
|
std::string callstack = this->callstack();
|
|
dynamic message = dynamic::object(
|
|
"error",
|
|
dynamic::object("message", e.what())("stacktrace", callstack)(
|
|
"name", e.what()));
|
|
socket_->sendMessage(message);
|
|
} else {
|
|
log("Error: " + std::string(e.what()));
|
|
}
|
|
} catch (...) {
|
|
// Generic catch block for the exception of type not belonging to
|
|
// std::exception
|
|
log("Unknown error suppressed in FlipperClient");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
std::string FlipperClient::getState() {
|
|
return flipperState_->getState();
|
|
}
|
|
|
|
std::vector<StateElement> FlipperClient::getStateElements() {
|
|
return flipperState_->getStateElements();
|
|
}
|
|
|
|
} // namespace flipper
|
|
} // namespace facebook
|
|
|
|
#endif
|