diff --git a/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactBaseSocket.h b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactBaseSocket.h new file mode 100644 index 000000000..84c49c687 --- /dev/null +++ b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactBaseSocket.h @@ -0,0 +1,93 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include "../../../../xplat/Flipper/FlipperScheduler.h" +#include "../../../../xplat/Flipper/FlipperTransportTypes.h" + +namespace facebook { +namespace flipper { + +class FlipperConnectionManager; +class ConnectionContextStore; +class FlipperReactBaseSocket { + public: + enum Status { + Unconnected, + Connecting, + Initializing, + Open, + ServerNotFound, + Failed, + Closed + }; + FlipperReactBaseSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler) + : endpoint_(std::move(endpoint)), + payload_(std::move(payload)), + scheduler_(scheduler), + connectionContextStore_(nullptr) {} + FlipperReactBaseSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler, + ConnectionContextStore* connectionContextStore) + : endpoint_(std::move(endpoint)), + payload_(std::move(payload)), + scheduler_(scheduler), + connectionContextStore_(connectionContextStore) {} + + FlipperReactBaseSocket(const FlipperReactBaseSocket&) = delete; + FlipperReactBaseSocket& operator=(const FlipperReactBaseSocket&) = delete; + + virtual ~FlipperReactBaseSocket() {} + + 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 payload_; + Scheduler* scheduler_; + ConnectionContextStore* connectionContextStore_; + + SocketEventHandler eventHandler_; + SocketMessageHandler messageHandler_; + std::atomic status_; +}; + +} // namespace flipper +} // namespace facebook diff --git a/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocket.cpp b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocket.cpp new file mode 100644 index 000000000..90ce96e64 --- /dev/null +++ b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocket.cpp @@ -0,0 +1,90 @@ +/* + * 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 "FlipperReactSocket.h" +#include +#include +#include +#include +#include +#include +#include "../../../../xplat/Flipper/ConnectionContextStore.h" +#include "../../../../xplat/Flipper/FlipperTransportTypes.h" +#include "../../../../xplat/Flipper/FlipperURLSerializer.h" +#include "../../../../xplat/Flipper/Log.h" +#include "FlipperReactSocketClient.h" + +namespace facebook { +namespace flipper { + +FlipperReactSocket::FlipperReactSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler) + : FlipperReactSocket( + std::move(endpoint), + std::move(payload), + scheduler, + nullptr) {} + +FlipperReactSocket::FlipperReactSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler, + ConnectionContextStore* connectionContextStore) { + if (endpoint.secure) { + socket_ = std::make_unique( + endpoint, std::move(payload), scheduler, connectionContextStore); + } else { + socket_ = std::make_unique( + endpoint, std::move(payload), scheduler, connectionContextStore); + } +} + +FlipperReactSocket::~FlipperReactSocket() {} + +void FlipperReactSocket::setEventHandler(SocketEventHandler eventHandler) { + socket_->setEventHandler(eventHandler); +} +void FlipperReactSocket::setMessageHandler( + SocketMessageHandler messageHandler) { + socket_->setMessageHandler(messageHandler); +} + +bool FlipperReactSocket::connect(FlipperConnectionManager* manager) { + return socket_->connect(manager); +} + +void FlipperReactSocket::disconnect() { + socket_->disconnect(); +} + +void FlipperReactSocket::send( + const folly::dynamic& message, + SocketSendHandler completion) { + socket_->send(message, completion); +} + +void FlipperReactSocket::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 FlipperReactSocket::sendExpectResponse( + const std::string& message, + SocketSendExpectResponseHandler completion) { + socket_->sendExpectResponse(message, completion); +} + +} // namespace flipper +} // namespace facebook diff --git a/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocket.h b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocket.h new file mode 100644 index 000000000..9f0523a03 --- /dev/null +++ b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocket.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include "../../../../xplat/Flipper/FlipperSocket.h" +#include "../../../../xplat/Flipper/FlipperSocketProvider.h" +#include "FlipperReactBaseSocket.h" + +namespace facebook { +namespace flipper { + +class FlipperConnectionManager; +class ConnectionContextStore; +class FlipperReactSocket : public FlipperSocket { + public: + FlipperReactSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler); + FlipperReactSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler, + ConnectionContextStore* connectionContextStore); + + virtual ~FlipperReactSocket(); + + 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 socket_; +}; + +class FlipperWebSocketProvider : public FlipperSocketProvider { + public: + FlipperWebSocketProvider() {} + virtual std::unique_ptr create( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler) override { + return std::make_unique( + std::move(endpoint), std::move(payload), scheduler); + } + virtual std::unique_ptr create( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler, + ConnectionContextStore* connectionContextStore) override { + return std::make_unique( + std::move(endpoint), + std::move(payload), + scheduler, + connectionContextStore); + } +}; + +} // namespace flipper +} // namespace facebook diff --git a/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocketClient.cpp b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocketClient.cpp new file mode 100644 index 000000000..b0ea8c107 --- /dev/null +++ b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocketClient.cpp @@ -0,0 +1,299 @@ +/* + * 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 "FlipperReactSocketClient.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../../../xplat/Flipper/ConnectionContextStore.h" +#include "../../../../xplat/Flipper/FlipperTransportTypes.h" +#include "../../../../xplat/Flipper/FlipperURLSerializer.h" +#include "../../../../xplat/Flipper/Log.h" + +using namespace winrt::Windows::Foundation; + +namespace facebook { +namespace flipper { + +static constexpr char* CERTIFICATE_FRIENDLY_NAME = "FlipperClientCertificate"; + +FlipperReactSocketClient::FlipperReactSocketClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler) + : FlipperReactSocketClient( + std::move(endpoint), + std::move(payload), + scheduler, + nullptr) {} + +FlipperReactSocketClient::FlipperReactSocketClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler, + ConnectionContextStore* connectionContextStore) + : FlipperReactBaseSocket( + std::move(endpoint), + std::move(payload), + scheduler, + connectionContextStore) { + status_ = Status::Unconnected; +} + +FlipperReactSocketClient::~FlipperReactSocketClient() { + disconnect(); +} + +winrt::Windows::Security::Cryptography::Certificates::Certificate +FlipperReactSocketClient::findClientCertificateFromStore() { + using namespace winrt::Windows::Foundation::Collections; + using namespace winrt::Windows::Security::Cryptography::Certificates; + + CertificateQuery query; + query.FriendlyName(winrt::to_hstring(CERTIFICATE_FRIENDLY_NAME)); + + try { + IVectorView certificates = + CertificateStores::FindAllAsync(query).get(); + if (certificates.Size() > 0) { + Certificate certificate = certificates.GetAt(0); + return certificate; + } + } catch (winrt::hresult_error const& ex) { + /* winrt::hresult_error can be thrown whilst trying to find certificates, + * ignore. */ + } + + throw std::exception("Unable to find client certificate"); +} + +winrt::Windows::Security::Cryptography::Certificates::Certificate +FlipperReactSocketClient::installClientCertificate() { + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Storage; + using namespace winrt::Windows::Security::Cryptography; + using namespace winrt::Windows::Security::Cryptography::Certificates; + + auto clientCertificateInfo = connectionContextStore_->getCertificate(); + if (clientCertificateInfo.first.empty() || + clientCertificateInfo.second.empty()) { + throw std::exception("Unable to generate PKCS12"); + } + + try { + StorageFile clientCertificateFile = + StorageFile::GetFileFromPathAsync( + winrt::to_hstring(clientCertificateInfo.first)) + .get(); + + auto buffer = FileIO::ReadBufferAsync(clientCertificateFile).get(); + winrt::hstring clientCertificateData = + CryptographicBuffer::EncodeToBase64String(buffer); + auto password = winrt::to_hstring(clientCertificateInfo.second); + + Certificates::CertificateEnrollmentManager::ImportPfxDataAsync( + clientCertificateData, + password, + ExportOption::Exportable, + KeyProtectionLevel::NoConsent, + InstallOptions::DeleteExpired, + winrt::to_hstring(CERTIFICATE_FRIENDLY_NAME)) + .get(); + + return findClientCertificateFromStore(); + } catch (winrt::hresult_error const& ex) { + /* winrt::hresult_error can be thrown whilst trying to install the + * certificate, ignore. */ + } + + throw std::exception("Unable to install client certificate"); +} + +winrt::Windows::Security::Cryptography::Certificates::Certificate +FlipperReactSocketClient::getClientCertificate() { + using namespace winrt::Windows::Security::Cryptography::Certificates; + try { + return findClientCertificateFromStore(); + } catch (const std::exception& ex) { + /* Client certificate may not be in the certificate store, + if so, try to install it. Ignore exception. */ + } + + return installClientCertificate(); +} + +bool FlipperReactSocketClient::connect(FlipperConnectionManager* manager) { + if (status_ != Status::Unconnected) { + return false; + } + + status_ = Status::Connecting; + + std::string connectionURL = endpoint_.secure ? "wss://" : "ws://"; + connectionURL += endpoint_.host.c_str(); + 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 = winrt::to_hstring(connectionURL); + + socket_.Control().MessageType( + winrt::Windows::Networking::Sockets::SocketMessageType::Utf8); + + if (endpoint_.secure) { + socket_.Control().ClientCertificate(getClientCertificate()); + socket_.Control().IgnorableServerCertificateErrors().Append( + winrt::Windows::Security::Cryptography::Certificates:: + ChainValidationResult::Untrusted); + socket_.Control().IgnorableServerCertificateErrors().Append( + winrt::Windows::Security::Cryptography::Certificates:: + ChainValidationResult::InvalidName); + } + + messageReceivedEventToken_ = socket_.MessageReceived( + {this, &FlipperReactSocketClient::OnWebSocketMessageReceived}); + closedEventToken_ = + socket_.Closed({this, &FlipperReactSocketClient::OnWebSocketClosed}); + + try { + this->socket_.ConnectAsync(winrt::Windows::Foundation::Uri(uri)) + .wait_for(std::chrono::seconds(10)); + + status_ = Status::Initializing; + scheduler_->schedule( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::OPEN); }); + + return true; + } catch (winrt::hresult_error const& ex) { + winrt::Windows::Web::WebErrorStatus webErrorStatus{ + winrt::Windows::Networking::Sockets::WebSocketError::GetStatus( + ex.to_abi())}; + } + + disconnect(); + + return false; +} + +void FlipperReactSocketClient::disconnect() { + status_ = Status::Closed; + scheduler_->schedule( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); }); + // socket_.Close(); + socket_ = nullptr; +} + +void FlipperReactSocketClient::send( + const folly::dynamic& message, + SocketSendHandler completion) { + std::string json = folly::toJson(message); + send(json, std::move(completion)); +} + +void FlipperReactSocketClient::send( + const std::string& message, + SocketSendHandler completion) { + using namespace winrt::Windows::Storage::Streams; + auto payload = winrt::to_hstring(message); + try { + DataWriter dataWriter{socket_.OutputStream()}; + dataWriter.WriteString(payload); + dataWriter.StoreAsync().get(); + dataWriter.DetachStream(); + + completion(); + } catch (winrt::hresult_error const& ex) { + } catch (const std::exception& ex) { + } +} + +/** + 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 FlipperReactSocketClient::sendExpectResponse( + const std::string& message, + SocketSendExpectResponseHandler completion) { + using namespace winrt::Windows::Storage::Streams; + + overrideHandler_ = + std::make_unique(completion); + + auto payload = winrt::to_hstring(message); + try { + DataWriter dataWriter{socket_.OutputStream()}; + dataWriter.WriteString(payload); + dataWriter.StoreAsync().get(); + dataWriter.DetachStream(); + } catch (winrt::hresult_error const& ex) { + overrideHandler_ = nullptr; + completion("", true); + } catch (const std::exception& ex) { + overrideHandler_ = nullptr; + completion(ex.what(), true); + } +} + +void FlipperReactSocketClient::OnWebSocketMessageReceived( + winrt::Windows::Networking::Sockets::MessageWebSocket const& /* sender */, + winrt::Windows::Networking::Sockets:: + MessageWebSocketMessageReceivedEventArgs const& args) { + using namespace winrt::Windows::Storage::Streams; + try { + DataReader dataReader{args.GetDataReader()}; + dataReader.UnicodeEncoding( + winrt::Windows::Storage::Streams::UnicodeEncoding::Utf8); + auto message = dataReader.ReadString(dataReader.UnconsumedBufferLength()); + + const std::string payload = winrt::to_string(message); + + if (overrideHandler_ != nullptr) { + scheduler_->schedule([payload, messageHandler = *overrideHandler_]() { + messageHandler(payload, false); + }); + overrideHandler_ = nullptr; + } else if (messageHandler_) { + scheduler_->schedule([payload, messageHandler = messageHandler_]() { + messageHandler(payload); + }); + } + } catch (winrt::hresult_error const& ex) { + // winrt::Windows::Web::WebErrorStatus webErrorStatus{ + // winrt::Windows::Networking::Sockets::WebSocketError::GetStatus(ex.to_abi())}; + } +} + +void FlipperReactSocketClient::OnWebSocketClosed( + winrt::Windows::Networking::Sockets::IWebSocket const& /* sender */, + winrt::Windows::Networking::Sockets::WebSocketClosedEventArgs const& args) { + if (status_ == Status::Closed) { + return; + } + status_ = Status::Closed; + scheduler_->schedule( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); }); +} + +} // namespace flipper +} // namespace facebook diff --git a/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocketClient.h b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocketClient.h new file mode 100644 index 000000000..44187aada --- /dev/null +++ b/react-native/react-native-flipper/windows/ReactNativeFlipper/FlipperReactSocketClient.h @@ -0,0 +1,80 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "../../../../xplat/Flipper/FlipperSocket.h" +#include "../../../../xplat/Flipper/FlipperSocketProvider.h" +#include "../../../../xplat/Flipper/FlipperTransportTypes.h" +#include "FlipperReactBaseSocket.h" + +namespace facebook { +namespace flipper { + +class FlipperConnectionManager; +class ConnectionContextStore; +class FlipperReactSocketClient : public FlipperReactBaseSocket { + public: + FlipperReactSocketClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler); + FlipperReactSocketClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + Scheduler* scheduler, + ConnectionContextStore* connectionContextStore); + + FlipperReactSocketClient(const FlipperReactSocketClient&) = delete; + FlipperReactSocketClient& operator=(const FlipperReactSocketClient&) = delete; + + virtual ~FlipperReactSocketClient(); + + 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; + + void OnWebSocketMessageReceived( + winrt::Windows::Networking::Sockets::MessageWebSocket const& /* sender */, + winrt::Windows::Networking::Sockets:: + MessageWebSocketMessageReceivedEventArgs const& args); + void OnWebSocketClosed( + winrt::Windows::Networking::Sockets::IWebSocket const& /* sender */, + winrt::Windows::Networking::Sockets::WebSocketClosedEventArgs const& + args); + + private: + std::promise connected_; + winrt::Windows::Networking::Sockets::MessageWebSocket socket_; + winrt::event_token messageReceivedEventToken_; + winrt::event_token closedEventToken_; + + winrt::Windows::Security::Cryptography::Certificates::Certificate + findClientCertificateFromStore(); + winrt::Windows::Security::Cryptography::Certificates::Certificate + installClientCertificate(); + winrt::Windows::Security::Cryptography::Certificates::Certificate + getClientCertificate(); + + std::unique_ptr overrideHandler_; +}; + +} // namespace flipper +} // namespace facebook