C++ WebSocket client for Flipper
Summary: Introducing a Flipper WebSocket client implemented in C++. The requirement came from Spark AR (Skylight) as they have a macOS/Linux/Windows clients. For reviewers: - This is an implementation of the existing FlipperSocket interface. Effectively, the only type that needs to be reviewed is WebSocketTLSClient. - BaseClient defined a base class for WebSocketClient and WebSocketTLSClient. - WebSocketClient is a simplified version of WebSocketTLSClient as there's no TLS configuration. Reviewed By: mweststrate Differential Revision: D34081943 fbshipit-source-id: 619a83f5a6783a21069d0f5111d139bb180f9e97
This commit is contained in:
committed by
Facebook GitHub Bot
parent
8696349593
commit
b3cf7e1ad1
98
xplat/FlipperWebSocket/BaseClient.h
Normal file
98
xplat/FlipperWebSocket/BaseClient.h
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef FB_SONARKIT_ENABLED
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Flipper/FlipperSocket.h>
|
||||||
|
#include <Flipper/FlipperSocketProvider.h>
|
||||||
|
#include <Flipper/FlipperTransportTypes.h>
|
||||||
|
#include <folly/dynamic.h>
|
||||||
|
#include <folly/io/async/EventBase.h>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace flipper {
|
||||||
|
|
||||||
|
class FlipperConnectionManager;
|
||||||
|
class ConnectionContextStore;
|
||||||
|
class BaseClient {
|
||||||
|
public:
|
||||||
|
enum Status {
|
||||||
|
Unconnected,
|
||||||
|
Connecting,
|
||||||
|
Initializing,
|
||||||
|
Open,
|
||||||
|
ServerNotFound,
|
||||||
|
Failed,
|
||||||
|
Closed
|
||||||
|
};
|
||||||
|
BaseClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase)
|
||||||
|
: endpoint_(std::move(endpoint)),
|
||||||
|
payload_(std::move(payload)),
|
||||||
|
eventBase_(eventBase) {}
|
||||||
|
BaseClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase,
|
||||||
|
ConnectionContextStore* connectionContextStore)
|
||||||
|
: endpoint_(std::move(endpoint)),
|
||||||
|
payload_(std::move(payload)),
|
||||||
|
eventBase_(eventBase),
|
||||||
|
connectionContextStore_(connectionContextStore) {}
|
||||||
|
|
||||||
|
BaseClient(const BaseClient&) = delete;
|
||||||
|
BaseClient& operator=(const BaseClient&) = delete;
|
||||||
|
|
||||||
|
virtual ~BaseClient() {}
|
||||||
|
|
||||||
|
Status status() const {
|
||||||
|
return status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEventHandler(SocketEventHandler eventHandler) {
|
||||||
|
eventHandler_ = std::move(eventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMessageHandler(SocketMessageHandler messageHandler) {
|
||||||
|
messageHandler_ = std::move(messageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool connect(FlipperConnectionManager* manager) = 0;
|
||||||
|
virtual void disconnect() = 0;
|
||||||
|
|
||||||
|
virtual void send(
|
||||||
|
const folly::dynamic& message,
|
||||||
|
SocketSendHandler completion) = 0;
|
||||||
|
virtual void send(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendHandler completion) = 0;
|
||||||
|
virtual void sendExpectResponse(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendExpectResponseHandler completion) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FlipperConnectionEndpoint endpoint_;
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload_;
|
||||||
|
folly::EventBase* eventBase_;
|
||||||
|
ConnectionContextStore* connectionContextStore_;
|
||||||
|
|
||||||
|
SocketEventHandler eventHandler_;
|
||||||
|
SocketMessageHandler messageHandler_;
|
||||||
|
std::atomic<Status> status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace flipper
|
||||||
|
} // namespace facebook
|
||||||
|
|
||||||
|
#endif
|
||||||
99
xplat/FlipperWebSocket/FlipperWebSocket.cpp
Normal file
99
xplat/FlipperWebSocket/FlipperWebSocket.cpp
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef FB_SONARKIT_ENABLED
|
||||||
|
|
||||||
|
#include "FlipperWebSocket.h"
|
||||||
|
#include <Flipper/ConnectionContextStore.h>
|
||||||
|
#include <Flipper/FlipperTransportTypes.h>
|
||||||
|
#include <Flipper/FlipperURLSerializer.h>
|
||||||
|
#include <Flipper/Log.h>
|
||||||
|
#include <folly/String.h>
|
||||||
|
#include <folly/futures/Future.h>
|
||||||
|
#include <folly/io/async/AsyncSocketException.h>
|
||||||
|
#include <folly/io/async/SSLContext.h>
|
||||||
|
#include <folly/json.h>
|
||||||
|
#include <cctype>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include "WebSocketClient.h"
|
||||||
|
#include "WebSocketTLSClient.h"
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace flipper {
|
||||||
|
|
||||||
|
FlipperWebSocket::FlipperWebSocket(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase)
|
||||||
|
: FlipperWebSocket(
|
||||||
|
std::move(endpoint),
|
||||||
|
std::move(payload),
|
||||||
|
eventBase,
|
||||||
|
nullptr) {}
|
||||||
|
|
||||||
|
FlipperWebSocket::FlipperWebSocket(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase,
|
||||||
|
ConnectionContextStore* connectionContextStore) {
|
||||||
|
if (endpoint.secure) {
|
||||||
|
socket_ = std::make_unique<WebSocketTLSClient>(
|
||||||
|
endpoint, std::move(payload), eventBase, connectionContextStore);
|
||||||
|
} else {
|
||||||
|
socket_ = std::make_unique<WebSocketClient>(
|
||||||
|
endpoint, std::move(payload), eventBase, connectionContextStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlipperWebSocket::~FlipperWebSocket() {}
|
||||||
|
|
||||||
|
void FlipperWebSocket::setEventHandler(SocketEventHandler eventHandler) {
|
||||||
|
socket_->setEventHandler(eventHandler);
|
||||||
|
}
|
||||||
|
void FlipperWebSocket::setMessageHandler(SocketMessageHandler messageHandler) {
|
||||||
|
socket_->setMessageHandler(messageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlipperWebSocket::connect(FlipperConnectionManager* manager) {
|
||||||
|
return socket_->connect(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlipperWebSocket::disconnect() {
|
||||||
|
socket_->disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlipperWebSocket::send(
|
||||||
|
const folly::dynamic& message,
|
||||||
|
SocketSendHandler completion) {
|
||||||
|
socket_->send(message, completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlipperWebSocket::send(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendHandler completion) {
|
||||||
|
socket_->send(message, completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Only ever used for insecure connections to receive the device_id from a
|
||||||
|
signCertificate request. If the intended usage ever changes, then a better
|
||||||
|
approach needs to be put in place.
|
||||||
|
*/
|
||||||
|
void FlipperWebSocket::sendExpectResponse(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendExpectResponseHandler completion) {
|
||||||
|
socket_->sendExpectResponse(message, completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace flipper
|
||||||
|
} // namespace facebook
|
||||||
|
|
||||||
|
#endif
|
||||||
85
xplat/FlipperWebSocket/FlipperWebSocket.h
Normal file
85
xplat/FlipperWebSocket/FlipperWebSocket.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef FB_SONARKIT_ENABLED
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Flipper/FlipperSocket.h>
|
||||||
|
#include <Flipper/FlipperSocketProvider.h>
|
||||||
|
#include <Flipper/FlipperTransportTypes.h>
|
||||||
|
#include <folly/dynamic.h>
|
||||||
|
#include <folly/io/async/EventBase.h>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include "BaseClient.h"
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace flipper {
|
||||||
|
|
||||||
|
class FlipperConnectionManager;
|
||||||
|
class ConnectionContextStore;
|
||||||
|
class FlipperWebSocket : public FlipperSocket {
|
||||||
|
public:
|
||||||
|
FlipperWebSocket(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase);
|
||||||
|
FlipperWebSocket(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase,
|
||||||
|
ConnectionContextStore* connectionContextStore);
|
||||||
|
|
||||||
|
virtual ~FlipperWebSocket();
|
||||||
|
|
||||||
|
virtual void setEventHandler(SocketEventHandler eventHandler) override;
|
||||||
|
virtual void setMessageHandler(SocketMessageHandler messageHandler) override;
|
||||||
|
|
||||||
|
virtual bool connect(FlipperConnectionManager* manager) override;
|
||||||
|
virtual void disconnect() override;
|
||||||
|
|
||||||
|
virtual void send(const folly::dynamic& message, SocketSendHandler completion)
|
||||||
|
override;
|
||||||
|
virtual void send(const std::string& message, SocketSendHandler completion)
|
||||||
|
override;
|
||||||
|
virtual void sendExpectResponse(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendExpectResponseHandler completion) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<BaseClient> socket_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FlipperWebSocketProvider : public FlipperSocketProvider {
|
||||||
|
public:
|
||||||
|
FlipperWebSocketProvider() {}
|
||||||
|
virtual std::unique_ptr<FlipperSocket> create(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase) override {
|
||||||
|
return std::make_unique<FlipperWebSocket>(
|
||||||
|
std::move(endpoint), std::move(payload), eventBase);
|
||||||
|
}
|
||||||
|
virtual std::unique_ptr<FlipperSocket> create(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase,
|
||||||
|
ConnectionContextStore* connectionContextStore) override {
|
||||||
|
return std::make_unique<FlipperWebSocket>(
|
||||||
|
std::move(endpoint),
|
||||||
|
std::move(payload),
|
||||||
|
eventBase,
|
||||||
|
connectionContextStore);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace flipper
|
||||||
|
} // namespace facebook
|
||||||
|
|
||||||
|
#endif
|
||||||
249
xplat/FlipperWebSocket/WebSocketClient.cpp
Normal file
249
xplat/FlipperWebSocket/WebSocketClient.cpp
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef FB_SONARKIT_ENABLED
|
||||||
|
|
||||||
|
#include "WebSocketClient.h"
|
||||||
|
#include <Flipper/ConnectionContextStore.h>
|
||||||
|
#include <Flipper/FlipperTransportTypes.h>
|
||||||
|
#include <Flipper/FlipperURLSerializer.h>
|
||||||
|
#include <Flipper/Log.h>
|
||||||
|
#include <folly/String.h>
|
||||||
|
#include <folly/futures/Future.h>
|
||||||
|
#include <folly/io/async/AsyncSocketException.h>
|
||||||
|
#include <folly/io/async/SSLContext.h>
|
||||||
|
#include <folly/json.h>
|
||||||
|
#include <websocketpp/common/memory.hpp>
|
||||||
|
#include <websocketpp/common/thread.hpp>
|
||||||
|
#include <cctype>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace flipper {
|
||||||
|
|
||||||
|
WebSocketClient::WebSocketClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase)
|
||||||
|
: WebSocketClient(
|
||||||
|
std::move(endpoint),
|
||||||
|
std::move(payload),
|
||||||
|
eventBase,
|
||||||
|
nullptr) {}
|
||||||
|
|
||||||
|
WebSocketClient::WebSocketClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase,
|
||||||
|
ConnectionContextStore* connectionContextStore)
|
||||||
|
: BaseClient(
|
||||||
|
std::move(endpoint),
|
||||||
|
std::move(payload),
|
||||||
|
eventBase,
|
||||||
|
connectionContextStore) {
|
||||||
|
status_ = Status::Unconnected;
|
||||||
|
|
||||||
|
socket_.clear_access_channels(websocketpp::log::alevel::all);
|
||||||
|
socket_.clear_error_channels(websocketpp::log::elevel::all);
|
||||||
|
socket_.init_asio();
|
||||||
|
socket_.start_perpetual();
|
||||||
|
|
||||||
|
thread_ = websocketpp::lib::make_shared<websocketpp::lib::thread>(
|
||||||
|
&SocketClient::run, &socket_);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketClient::~WebSocketClient() {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebSocketClient::connect(FlipperConnectionManager* manager) {
|
||||||
|
if (status_ != Status::Unconnected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_ = Status::Connecting;
|
||||||
|
|
||||||
|
std::string connectionURL = endpoint_.secure ? "wss://" : "ws://";
|
||||||
|
connectionURL += endpoint_.host;
|
||||||
|
connectionURL += ":";
|
||||||
|
connectionURL += std::to_string(endpoint_.port);
|
||||||
|
|
||||||
|
auto serializer = URLSerializer{};
|
||||||
|
payload_->serialize(serializer);
|
||||||
|
auto payload = serializer.serialize();
|
||||||
|
|
||||||
|
if (payload.size()) {
|
||||||
|
connectionURL += "/?";
|
||||||
|
connectionURL += payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto uri = websocketpp::lib::make_shared<websocketpp::uri>(connectionURL);
|
||||||
|
websocketpp::lib::error_code ec;
|
||||||
|
connection_ = socket_.get_connection(uri, ec);
|
||||||
|
|
||||||
|
if (ec) {
|
||||||
|
status_ = Status::Failed;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_ = connection_->get_handle();
|
||||||
|
|
||||||
|
connection_->set_open_handler(websocketpp::lib::bind(
|
||||||
|
&WebSocketClient::onOpen,
|
||||||
|
this,
|
||||||
|
&socket_,
|
||||||
|
websocketpp::lib::placeholders::_1));
|
||||||
|
|
||||||
|
connection_->set_message_handler(websocketpp::lib::bind(
|
||||||
|
&WebSocketClient::onMessage,
|
||||||
|
this,
|
||||||
|
&socket_,
|
||||||
|
websocketpp::lib::placeholders::_1,
|
||||||
|
websocketpp::lib::placeholders::_2));
|
||||||
|
|
||||||
|
connection_->set_fail_handler(websocketpp::lib::bind(
|
||||||
|
&WebSocketClient::onFail,
|
||||||
|
this,
|
||||||
|
&socket_,
|
||||||
|
websocketpp::lib::placeholders::_1));
|
||||||
|
|
||||||
|
connection_->set_close_handler(websocketpp::lib::bind(
|
||||||
|
&WebSocketClient::onClose,
|
||||||
|
this,
|
||||||
|
&socket_,
|
||||||
|
websocketpp::lib::placeholders::_1));
|
||||||
|
|
||||||
|
auto connected = connected_.get_future();
|
||||||
|
|
||||||
|
socket_.connect(connection_);
|
||||||
|
|
||||||
|
auto state = connected.wait_for(std::chrono::seconds(10));
|
||||||
|
if (state == std::future_status::ready) {
|
||||||
|
return connected.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::disconnect() {
|
||||||
|
socket_.stop_perpetual();
|
||||||
|
|
||||||
|
if (status_ == Status::Connecting || status_ == Status::Open ||
|
||||||
|
status_ == Status::Failed) {
|
||||||
|
websocketpp::lib::error_code ec;
|
||||||
|
socket_.close(handle_, websocketpp::close::status::going_away, "", ec);
|
||||||
|
}
|
||||||
|
socket_.stop();
|
||||||
|
status_ = Status::Closed;
|
||||||
|
if (thread_ && thread_->joinable()) {
|
||||||
|
thread_->join();
|
||||||
|
}
|
||||||
|
thread_ = nullptr;
|
||||||
|
eventBase_->add(
|
||||||
|
[eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::send(
|
||||||
|
const folly::dynamic& message,
|
||||||
|
SocketSendHandler completion) {
|
||||||
|
std::string json = folly::toJson(message);
|
||||||
|
send(json, std::move(completion));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::send(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendHandler completion) {
|
||||||
|
websocketpp::lib::error_code ec;
|
||||||
|
socket_.send(
|
||||||
|
handle_,
|
||||||
|
&message[0],
|
||||||
|
message.size(),
|
||||||
|
websocketpp::frame::opcode::text,
|
||||||
|
ec);
|
||||||
|
completion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Only ever used for insecure connections to receive the device_id from a
|
||||||
|
signCertificate request. If the intended usage ever changes, then a better
|
||||||
|
approach needs to be put in place.
|
||||||
|
*/
|
||||||
|
void WebSocketClient::sendExpectResponse(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendExpectResponseHandler completion) {
|
||||||
|
connection_->set_message_handler(
|
||||||
|
[completion, eventBase = eventBase_](
|
||||||
|
websocketpp::connection_hdl hdl, SocketClient::message_ptr msg) {
|
||||||
|
const std::string& payload = msg->get_payload();
|
||||||
|
eventBase->add([completion, payload] { completion(payload, false); });
|
||||||
|
});
|
||||||
|
websocketpp::lib::error_code ec;
|
||||||
|
socket_.send(
|
||||||
|
handle_,
|
||||||
|
&message[0],
|
||||||
|
message.size(),
|
||||||
|
websocketpp::frame::opcode::text,
|
||||||
|
ec);
|
||||||
|
if (ec) {
|
||||||
|
auto reason = ec.message();
|
||||||
|
completion(reason, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::onOpen(SocketClient* c, websocketpp::connection_hdl hdl) {
|
||||||
|
if (status_ == Status::Connecting) {
|
||||||
|
connected_.set_value(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_ = Status::Initializing;
|
||||||
|
eventBase_->add(
|
||||||
|
[eventHandler = eventHandler_]() { eventHandler(SocketEvent::OPEN); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::onMessage(
|
||||||
|
SocketClient* c,
|
||||||
|
websocketpp::connection_hdl hdl,
|
||||||
|
SocketClient::message_ptr msg) {
|
||||||
|
const std::string& payload = msg->get_payload();
|
||||||
|
if (messageHandler_) {
|
||||||
|
eventBase_->add([payload, messageHandler = messageHandler_]() {
|
||||||
|
messageHandler(payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::onFail(SocketClient* c, websocketpp::connection_hdl hdl) {
|
||||||
|
SocketClient::connection_ptr con = c->get_con_from_hdl(hdl);
|
||||||
|
auto server = con->get_response_header("Server");
|
||||||
|
auto reason = con->get_ec().message();
|
||||||
|
|
||||||
|
if (status_ == Status::Connecting) {
|
||||||
|
connected_.set_value(false);
|
||||||
|
}
|
||||||
|
status_ = Status::Failed;
|
||||||
|
eventBase_->add(
|
||||||
|
[eventHandler = eventHandler_]() { eventHandler(SocketEvent::ERROR); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::onClose(
|
||||||
|
SocketClient* c,
|
||||||
|
websocketpp::connection_hdl hdl) {
|
||||||
|
status_ = Status::Closed;
|
||||||
|
eventBase_->add(
|
||||||
|
[eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace flipper
|
||||||
|
} // namespace facebook
|
||||||
|
|
||||||
|
#endif
|
||||||
81
xplat/FlipperWebSocket/WebSocketClient.h
Normal file
81
xplat/FlipperWebSocket/WebSocketClient.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef FB_SONARKIT_ENABLED
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Flipper/FlipperSocket.h>
|
||||||
|
#include <Flipper/FlipperSocketProvider.h>
|
||||||
|
#include <Flipper/FlipperTransportTypes.h>
|
||||||
|
#include <folly/dynamic.h>
|
||||||
|
#include <folly/io/async/EventBase.h>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include "BaseClient.h"
|
||||||
|
|
||||||
|
#include <websocketpp/client.hpp>
|
||||||
|
#include <websocketpp/config/asio_no_tls_client.hpp>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace flipper {
|
||||||
|
|
||||||
|
typedef websocketpp::client<websocketpp::config::asio_client> SocketClient;
|
||||||
|
typedef websocketpp::lib::shared_ptr<websocketpp::lib::thread> SocketThread;
|
||||||
|
|
||||||
|
class FlipperConnectionManager;
|
||||||
|
class ConnectionContextStore;
|
||||||
|
class WebSocketClient : public BaseClient {
|
||||||
|
public:
|
||||||
|
WebSocketClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase);
|
||||||
|
WebSocketClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase,
|
||||||
|
ConnectionContextStore* connectionContextStore);
|
||||||
|
|
||||||
|
WebSocketClient(const WebSocketClient&) = delete;
|
||||||
|
WebSocketClient& operator=(const WebSocketClient&) = delete;
|
||||||
|
|
||||||
|
virtual ~WebSocketClient();
|
||||||
|
|
||||||
|
virtual bool connect(FlipperConnectionManager* manager) override;
|
||||||
|
virtual void disconnect() override;
|
||||||
|
|
||||||
|
virtual void send(const folly::dynamic& message, SocketSendHandler completion)
|
||||||
|
override;
|
||||||
|
virtual void send(const std::string& message, SocketSendHandler completion)
|
||||||
|
override;
|
||||||
|
virtual void sendExpectResponse(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendExpectResponseHandler completion) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onOpen(SocketClient*, websocketpp::connection_hdl);
|
||||||
|
void onMessage(
|
||||||
|
SocketClient*,
|
||||||
|
websocketpp::connection_hdl,
|
||||||
|
SocketClient::message_ptr);
|
||||||
|
void onFail(SocketClient*, websocketpp::connection_hdl);
|
||||||
|
void onClose(SocketClient*, websocketpp::connection_hdl);
|
||||||
|
|
||||||
|
SocketClient socket_;
|
||||||
|
SocketClient::connection_ptr connection_;
|
||||||
|
|
||||||
|
SocketThread thread_;
|
||||||
|
websocketpp::connection_hdl handle_;
|
||||||
|
std::promise<bool> connected_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace flipper
|
||||||
|
} // namespace facebook
|
||||||
|
|
||||||
|
#endif
|
||||||
313
xplat/FlipperWebSocket/WebSocketTLSClient.cpp
Normal file
313
xplat/FlipperWebSocket/WebSocketTLSClient.cpp
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef FB_SONARKIT_ENABLED
|
||||||
|
|
||||||
|
#include "WebSocketTLSClient.h"
|
||||||
|
#include <Flipper/ConnectionContextStore.h>
|
||||||
|
#include <Flipper/FlipperTransportTypes.h>
|
||||||
|
#include <Flipper/FlipperURLSerializer.h>
|
||||||
|
#include <Flipper/Log.h>
|
||||||
|
#include <folly/String.h>
|
||||||
|
#include <folly/futures/Future.h>
|
||||||
|
#include <folly/io/async/AsyncSocketException.h>
|
||||||
|
#include <folly/io/async/SSLContext.h>
|
||||||
|
#include <folly/json.h>
|
||||||
|
#include <websocketpp/common/memory.hpp>
|
||||||
|
#include <websocketpp/common/thread.hpp>
|
||||||
|
#include <cctype>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace flipper {
|
||||||
|
|
||||||
|
WebSocketTLSClient::WebSocketTLSClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase)
|
||||||
|
: WebSocketTLSClient(
|
||||||
|
std::move(endpoint),
|
||||||
|
std::move(payload),
|
||||||
|
eventBase,
|
||||||
|
nullptr) {}
|
||||||
|
|
||||||
|
WebSocketTLSClient::WebSocketTLSClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase,
|
||||||
|
ConnectionContextStore* connectionContextStore)
|
||||||
|
: BaseClient(
|
||||||
|
std::move(endpoint),
|
||||||
|
std::move(payload),
|
||||||
|
eventBase,
|
||||||
|
connectionContextStore) {
|
||||||
|
status_ = Status::Unconnected;
|
||||||
|
|
||||||
|
socket_.clear_access_channels(websocketpp::log::alevel::all);
|
||||||
|
socket_.clear_error_channels(websocketpp::log::elevel::all);
|
||||||
|
socket_.init_asio();
|
||||||
|
socket_.start_perpetual();
|
||||||
|
|
||||||
|
thread_ = websocketpp::lib::make_shared<websocketpp::lib::thread>(
|
||||||
|
&SocketTLSClient::run, &socket_);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketTLSClient::~WebSocketTLSClient() {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebSocketTLSClient::connect(FlipperConnectionManager* manager) {
|
||||||
|
if (status_ != Status::Unconnected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_ = Status::Connecting;
|
||||||
|
|
||||||
|
std::string connectionURL = endpoint_.secure ? "wss://" : "ws://";
|
||||||
|
connectionURL += endpoint_.host;
|
||||||
|
connectionURL += ":";
|
||||||
|
connectionURL += std::to_string(endpoint_.port);
|
||||||
|
|
||||||
|
auto serializer = URLSerializer{};
|
||||||
|
payload_->serialize(serializer);
|
||||||
|
auto payload = serializer.serialize();
|
||||||
|
|
||||||
|
if (payload.size()) {
|
||||||
|
connectionURL += "/?";
|
||||||
|
connectionURL += payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_.set_tls_init_handler(bind(
|
||||||
|
&WebSocketTLSClient::onTLSInit,
|
||||||
|
this,
|
||||||
|
endpoint_.host.c_str(),
|
||||||
|
websocketpp::lib::placeholders::_1));
|
||||||
|
|
||||||
|
auto uri = websocketpp::lib::make_shared<websocketpp::uri>(connectionURL);
|
||||||
|
websocketpp::lib::error_code ec;
|
||||||
|
connection_ = socket_.get_connection(uri, ec);
|
||||||
|
|
||||||
|
if (ec) {
|
||||||
|
status_ = Status::Failed;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_ = connection_->get_handle();
|
||||||
|
|
||||||
|
connection_->set_open_handler(websocketpp::lib::bind(
|
||||||
|
&WebSocketTLSClient::onOpen,
|
||||||
|
this,
|
||||||
|
&socket_,
|
||||||
|
websocketpp::lib::placeholders::_1));
|
||||||
|
|
||||||
|
connection_->set_message_handler(websocketpp::lib::bind(
|
||||||
|
&WebSocketTLSClient::onMessage,
|
||||||
|
this,
|
||||||
|
&socket_,
|
||||||
|
websocketpp::lib::placeholders::_1,
|
||||||
|
websocketpp::lib::placeholders::_2));
|
||||||
|
|
||||||
|
connection_->set_fail_handler(websocketpp::lib::bind(
|
||||||
|
&WebSocketTLSClient::onFail,
|
||||||
|
this,
|
||||||
|
&socket_,
|
||||||
|
websocketpp::lib::placeholders::_1));
|
||||||
|
|
||||||
|
connection_->set_close_handler(websocketpp::lib::bind(
|
||||||
|
&WebSocketTLSClient::onClose,
|
||||||
|
this,
|
||||||
|
&socket_,
|
||||||
|
websocketpp::lib::placeholders::_1));
|
||||||
|
|
||||||
|
auto connected = connected_.get_future();
|
||||||
|
|
||||||
|
socket_.connect(connection_);
|
||||||
|
|
||||||
|
auto state = connected.wait_for(std::chrono::seconds(10));
|
||||||
|
if (state == std::future_status::ready) {
|
||||||
|
return connected.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTLSClient::disconnect() {
|
||||||
|
socket_.stop_perpetual();
|
||||||
|
|
||||||
|
if (status_ == Status::Connecting || status_ == Status::Open ||
|
||||||
|
status_ == Status::Failed) {
|
||||||
|
websocketpp::lib::error_code ec;
|
||||||
|
socket_.close(handle_, websocketpp::close::status::going_away, "", ec);
|
||||||
|
}
|
||||||
|
socket_.stop();
|
||||||
|
status_ = Status::Closed;
|
||||||
|
if (thread_ && thread_->joinable()) {
|
||||||
|
thread_->join();
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_ = nullptr;
|
||||||
|
eventBase_->add(
|
||||||
|
[eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTLSClient::send(
|
||||||
|
const folly::dynamic& message,
|
||||||
|
SocketSendHandler completion) {
|
||||||
|
std::string json = folly::toJson(message);
|
||||||
|
send(json, std::move(completion));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTLSClient::send(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendHandler completion) {
|
||||||
|
websocketpp::lib::error_code ec;
|
||||||
|
socket_.send(
|
||||||
|
handle_,
|
||||||
|
&message[0],
|
||||||
|
message.size(),
|
||||||
|
websocketpp::frame::opcode::text,
|
||||||
|
ec);
|
||||||
|
completion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Only ever used for insecure connections to receive the device_id from a
|
||||||
|
signCertificate request. If the intended usage ever changes, then a better
|
||||||
|
approach needs to be put in place.
|
||||||
|
*/
|
||||||
|
void WebSocketTLSClient::sendExpectResponse(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendExpectResponseHandler completion) {
|
||||||
|
connection_->set_message_handler(
|
||||||
|
[completion, eventBase = eventBase_](
|
||||||
|
websocketpp::connection_hdl hdl, SocketTLSClient::message_ptr msg) {
|
||||||
|
const std::string& payload = msg->get_payload();
|
||||||
|
eventBase->add([completion, payload] { completion(payload, false); });
|
||||||
|
});
|
||||||
|
websocketpp::lib::error_code ec;
|
||||||
|
socket_.send(
|
||||||
|
handle_,
|
||||||
|
&message[0],
|
||||||
|
message.size(),
|
||||||
|
websocketpp::frame::opcode::text,
|
||||||
|
ec);
|
||||||
|
if (ec) {
|
||||||
|
auto reason = ec.message();
|
||||||
|
completion(reason, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTLSClient::onOpen(
|
||||||
|
SocketTLSClient* c,
|
||||||
|
websocketpp::connection_hdl hdl) {
|
||||||
|
if (status_ == Status::Connecting) {
|
||||||
|
connected_.set_value(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_ = Status::Initializing;
|
||||||
|
eventBase_->add(
|
||||||
|
[eventHandler = eventHandler_]() { eventHandler(SocketEvent::OPEN); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTLSClient::onMessage(
|
||||||
|
SocketTLSClient* c,
|
||||||
|
websocketpp::connection_hdl hdl,
|
||||||
|
SocketTLSClient::message_ptr msg) {
|
||||||
|
const std::string& payload = msg->get_payload();
|
||||||
|
if (messageHandler_) {
|
||||||
|
eventBase_->add([payload, messageHandler = messageHandler_]() {
|
||||||
|
messageHandler(payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTLSClient::onFail(
|
||||||
|
SocketTLSClient* c,
|
||||||
|
websocketpp::connection_hdl hdl) {
|
||||||
|
SocketTLSClient::connection_ptr con = c->get_con_from_hdl(hdl);
|
||||||
|
auto server = con->get_response_header("Server");
|
||||||
|
auto reason = con->get_ec().message();
|
||||||
|
auto sslError =
|
||||||
|
(reason.find("TLS handshake failed") != std::string::npos ||
|
||||||
|
reason.find("Generic TLS related error") != std::string::npos);
|
||||||
|
|
||||||
|
if (status_ == Status::Connecting) {
|
||||||
|
if (sslError) {
|
||||||
|
try {
|
||||||
|
connected_.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.
|
||||||
|
connected_.set_value(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
connected_.set_value(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status_ = Status::Failed;
|
||||||
|
eventBase_->add([eventHandler = eventHandler_, sslError]() {
|
||||||
|
if (sslError) {
|
||||||
|
eventHandler(SocketEvent::SSL_ERROR);
|
||||||
|
} else {
|
||||||
|
eventHandler(SocketEvent::ERROR);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTLSClient::onClose(
|
||||||
|
SocketTLSClient* c,
|
||||||
|
websocketpp::connection_hdl hdl) {
|
||||||
|
status_ = Status::Closed;
|
||||||
|
eventBase_->add(
|
||||||
|
[eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); });
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketTLSContext WebSocketTLSClient::onTLSInit(
|
||||||
|
const char* hostname,
|
||||||
|
websocketpp::connection_hdl) {
|
||||||
|
SocketTLSContext ctx =
|
||||||
|
websocketpp::lib::make_shared<boost::asio::ssl::context>(
|
||||||
|
boost::asio::ssl::context::sslv23);
|
||||||
|
|
||||||
|
ctx->set_options(
|
||||||
|
boost::asio::ssl::context::default_workarounds |
|
||||||
|
boost::asio::ssl::context::no_sslv2 |
|
||||||
|
boost::asio::ssl::context::no_sslv3 |
|
||||||
|
boost::asio::ssl::context::single_dh_use);
|
||||||
|
|
||||||
|
ctx->set_verify_mode(boost::asio::ssl::verify_peer);
|
||||||
|
ctx->load_verify_file(connectionContextStore_->getPath(
|
||||||
|
ConnectionContextStore::StoreItem::FLIPPER_CA));
|
||||||
|
|
||||||
|
boost::system::error_code error;
|
||||||
|
ctx->use_certificate_file(
|
||||||
|
connectionContextStore_->getPath(
|
||||||
|
ConnectionContextStore::StoreItem::CLIENT_CERT),
|
||||||
|
boost::asio::ssl::context::pem,
|
||||||
|
error);
|
||||||
|
ctx->use_private_key_file(
|
||||||
|
connectionContextStore_->getPath(
|
||||||
|
ConnectionContextStore::StoreItem::PRIVATE_KEY),
|
||||||
|
boost::asio::ssl::context::pem,
|
||||||
|
error);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace flipper
|
||||||
|
} // namespace facebook
|
||||||
|
|
||||||
|
#endif
|
||||||
86
xplat/FlipperWebSocket/WebSocketTLSClient.h
Normal file
86
xplat/FlipperWebSocket/WebSocketTLSClient.h
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef FB_SONARKIT_ENABLED
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Flipper/FlipperSocket.h>
|
||||||
|
#include <Flipper/FlipperSocketProvider.h>
|
||||||
|
#include <Flipper/FlipperTransportTypes.h>
|
||||||
|
#include <folly/dynamic.h>
|
||||||
|
#include <folly/io/async/EventBase.h>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include "BaseClient.h"
|
||||||
|
|
||||||
|
#include <websocketpp/client.hpp>
|
||||||
|
#include <websocketpp/config/asio_client.hpp>
|
||||||
|
#include <websocketpp/config/asio_no_tls_client.hpp>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace flipper {
|
||||||
|
|
||||||
|
typedef websocketpp::client<websocketpp::config::asio_tls_client>
|
||||||
|
SocketTLSClient;
|
||||||
|
typedef websocketpp::lib::shared_ptr<websocketpp::lib::thread> SocketTLSThread;
|
||||||
|
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>
|
||||||
|
SocketTLSContext;
|
||||||
|
|
||||||
|
class FlipperConnectionManager;
|
||||||
|
class ConnectionContextStore;
|
||||||
|
class WebSocketTLSClient : public BaseClient {
|
||||||
|
public:
|
||||||
|
WebSocketTLSClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase);
|
||||||
|
WebSocketTLSClient(
|
||||||
|
FlipperConnectionEndpoint endpoint,
|
||||||
|
std::unique_ptr<FlipperSocketBasePayload> payload,
|
||||||
|
folly::EventBase* eventBase,
|
||||||
|
ConnectionContextStore* connectionContextStore);
|
||||||
|
|
||||||
|
WebSocketTLSClient(const WebSocketTLSClient&) = delete;
|
||||||
|
WebSocketTLSClient& operator=(const WebSocketTLSClient&) = delete;
|
||||||
|
|
||||||
|
virtual ~WebSocketTLSClient();
|
||||||
|
|
||||||
|
virtual bool connect(FlipperConnectionManager* manager) override;
|
||||||
|
virtual void disconnect() override;
|
||||||
|
|
||||||
|
virtual void send(const folly::dynamic& message, SocketSendHandler completion)
|
||||||
|
override;
|
||||||
|
virtual void send(const std::string& message, SocketSendHandler completion)
|
||||||
|
override;
|
||||||
|
virtual void sendExpectResponse(
|
||||||
|
const std::string& message,
|
||||||
|
SocketSendExpectResponseHandler completion) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onOpen(SocketTLSClient*, websocketpp::connection_hdl);
|
||||||
|
void onMessage(
|
||||||
|
SocketTLSClient*,
|
||||||
|
websocketpp::connection_hdl,
|
||||||
|
SocketTLSClient::message_ptr);
|
||||||
|
void onFail(SocketTLSClient*, websocketpp::connection_hdl);
|
||||||
|
void onClose(SocketTLSClient*, websocketpp::connection_hdl);
|
||||||
|
SocketTLSContext onTLSInit(const char*, websocketpp::connection_hdl);
|
||||||
|
|
||||||
|
SocketTLSClient socket_;
|
||||||
|
SocketTLSClient::connection_ptr connection_;
|
||||||
|
|
||||||
|
SocketTLSThread thread_;
|
||||||
|
websocketpp::connection_hdl handle_;
|
||||||
|
std::promise<bool> connected_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace flipper
|
||||||
|
} // namespace facebook
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user