diff --git a/.vscode/settings.json b/.vscode/settings.json index 72f1efd6407..9fc3b99723a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,7 +23,7 @@ "**/lib/**/*.js": true, "**/node_modules": false }, - "editor.formatOnSave": true, + "editor.formatOnSave": false, "eslint.format.enable": true, "eslint.packageManager": "yarn", "eslint.enable": true, diff --git a/change/react-native-windows-55a74ced-a538-484a-98a1-8cb1b4e939b7.json b/change/react-native-windows-55a74ced-a538-484a-98a1-8cb1b4e939b7.json new file mode 100644 index 00000000000..98f65462a5e --- /dev/null +++ b/change/react-native-windows-55a74ced-a538-484a-98a1-8cb1b4e939b7.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Use TurboModules for networking in MSRN (#11867)", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/playground/windows/playground-win32-packaged.sln b/packages/playground/windows/playground-win32-packaged.sln index 1fe18bc49c2..a76a97e8b21 100644 --- a/packages/playground/windows/playground-win32-packaged.sln +++ b/packages/playground/windows/playground-win32-packaged.sln @@ -44,12 +44,12 @@ Global ..\..\..\vnext\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM = Debug|ARM Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 - Release|ARM = Release|ARM + Debug|ARM = Debug|ARM Release|x64 = Release|x64 Release|x86 = Release|x86 + Release|ARM = Release|ARM EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8B88FFAE-4DBC-49A2-AFA5-D2477D4AD189}.Debug|ARM.ActiveCfg = Debug|Win32 diff --git a/packages/playground/windows/playground-win32.sln b/packages/playground/windows/playground-win32.sln index 1ef1890150c..d8a150d7efd 100644 --- a/packages/playground/windows/playground-win32.sln +++ b/packages/playground/windows/playground-win32.sln @@ -44,12 +44,12 @@ Global ..\..\..\vnext\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM = Debug|ARM Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 - Release|ARM = Release|ARM + Debug|ARM = Debug|ARM Release|x64 = Release|x64 Release|x86 = Release|x86 + Release|ARM = Release|ARM EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8B88FFAE-4DBC-49A2-AFA5-D2477D4AD189}.Debug|ARM.ActiveCfg = Debug|Win32 diff --git a/vnext/.editorconfig b/vnext/.editorconfig index 2cec6e9f5b6..d49049a8967 100644 --- a/vnext/.editorconfig +++ b/vnext/.editorconfig @@ -28,5 +28,8 @@ csharp_prefer_simple_default_expression = false:none indent_style = tab indent_size = 4 +[overrides.json] +insert_final_newline = false + [package.json] insert_final_newline = false diff --git a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp index c9026cc9831..44cfe5f2fdb 100644 --- a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp +++ b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp @@ -15,7 +15,6 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace http = boost::beast::http; -using folly::dynamic; using Microsoft::React::Networking::IHttpResource; using Microsoft::React::Networking::OriginPolicy; using std::make_shared; @@ -146,10 +145,10 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) string{server1Args.Url}, 0, /*requestId*/ std::move(clientArgs.RequestHeaders), - dynamic::object("string", ""), /*data*/ + { { "string", "" } }, /*data*/ "text", false, /*useIncrementalUpdates*/ - 0, /*timeout*/ + 0, /*timeout*/ clientArgs.WithCredentials, /*withCredentials*/ [](int64_t){} /*reactCallback*/ ); @@ -200,10 +199,10 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) string{serverArgs.Url}, 0, /*requestId*/ std::move(clientArgs.RequestHeaders), - dynamic::object("string", ""), /*data*/ + { { "string", "" } }, /*data*/ "text", false, /*useIncrementalUpdates*/ - 0, /*timeout*/ + 0, /*timeout*/ clientArgs.WithCredentials, /*withCredentials*/ [](int64_t) {} /*reactCallback*/ ); @@ -298,12 +297,12 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) { {"ValidHeader", "AnyValue"} }, - dynamic::object("string", ""), /*data*/ + { { "string", "" } }, /*data*/ "text", - false /*useIncrementalUpdates*/, - 0 /*timeout*/, - false /*withCredentials*/, - [](int64_t) {} /*callback*/ + false /*useIncrementalUpdates*/, + 0 /*timeout*/, + false /*withCredentials*/, + [](int64_t) {} /*callback*/ ); getDataPromise.get_future().wait(); diff --git a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp index 172ccb41b4a..5a4e3e46ba3 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp @@ -26,7 +26,6 @@ TEST_MODULE_INITIALIZE(InitModule) { using Microsoft::React::SetRuntimeOptionBool; SetRuntimeOptionBool("WebSocket.AcceptSelfSigned", true); - SetRuntimeOptionBool("UseBeastWebSocket", false); SetRuntimeOptionBool("Blob.EnableModule", true); // WebSocketJSExecutor can't register native log hooks. diff --git a/vnext/Desktop.IntegrationTests/React.Windows.Desktop.IntegrationTests.vcxproj b/vnext/Desktop.IntegrationTests/React.Windows.Desktop.IntegrationTests.vcxproj index 9a4a31503bd..a893bd9caf8 100644 --- a/vnext/Desktop.IntegrationTests/React.Windows.Desktop.IntegrationTests.vcxproj +++ b/vnext/Desktop.IntegrationTests/React.Windows.Desktop.IntegrationTests.vcxproj @@ -36,6 +36,7 @@ + @@ -47,22 +48,32 @@ true - + BOOST_ASIO_HAS_IOCP; + CORE_ABI; _WIN32_WINNT=$(WinVer); WIN32; _WINDOWS; FOLLY_NO_CONFIG; NOMINMAX; _HAS_AUTO_PTR_ETC; - RN_PLATFORM=windesktop; + RN_PLATFORM=win32; RN_EXPORT=; JSI_EXPORT=; %(PreprocessorDefinitions) %(AdditionalOptions) /bigobj - $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + + $(VCInstallDir)UnitTest\include; + "$(ReactNativeWindowsDir)Microsoft.ReactNative"; + "$(ReactNativeWindowsDir)\Shared\tracing"; + %(AdditionalIncludeDirectories) + true @@ -128,4 +139,23 @@ + + + + + $(OutputPath)..\React.Windows.Desktop\facebook.react.winmd + + + $(OutputPath)..\React.Windows.Desktop\Microsoft.Internal.winmd + + + $(OutputPath)..\React.Windows.Desktop\Microsoft.ReactNative.winmd + + + \ No newline at end of file diff --git a/vnext/Desktop.UnitTests/BaseWebSocketTests.cpp b/vnext/Desktop.UnitTests/BaseWebSocketTests.cpp deleted file mode 100644 index c5e68faa19a..00000000000 --- a/vnext/Desktop.UnitTests/BaseWebSocketTests.cpp +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include -#include -#include - -using namespace boost::beast; -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -using boost::system::error_code; -using Microsoft::React::Beast::Test::TestWebSocketResource; -using std::future; -using std::make_unique; -using std::promise; -using std::string; - -namespace { - -constexpr char testUrl[] = "ws://localhost"; - -} // namespace - -namespace Microsoft::React::Test { - -using CloseCode = IWebSocketResource::CloseCode; -using Error = IWebSocketResource::Error; - -// We turn clang format off here because it does not work with some of the -// test macros. -// clang-format off - -TEST_CLASS(BaseWebSocketTest){ - BEGIN_TEST_CLASS_ATTRIBUTE() - TEST_CLASS_ATTRIBUTE(L"Ignore", L"true") - END_TEST_CLASS_ATTRIBUTE() - - TEST_METHOD(CreateAndSetHandlers){ - auto ws = make_unique(Url(testUrl)); - - Assert::IsFalse(nullptr == ws); - ws->SetOnConnect([]() {}); - ws->SetOnPing([]() {}); - ws->SetOnSend([](size_t length) {}); - ws->SetOnMessage([](size_t length, const string &buffer, bool isBinary) {}); - ws->SetOnClose([](CloseCode, const string &) {}); - ws->SetOnError([](const Error &error) {}); - } - - TEST_METHOD(ConnectSucceeds) { - string errorMessage; - bool connected = false; - auto ws = make_unique(Url(testUrl)); - ws->SetOnError([&errorMessage](Error err) { errorMessage = err.Message; }); - ws->SetOnConnect([&connected]() { connected = true; }); - - ws->Connect(testUrl, {}, {}); - ws->Close(CloseCode::Normal, {}); - - Assert::AreEqual({}, errorMessage); - Assert::IsTrue(connected); - } - - TEST_METHOD(ConnectFails) { - string errorMessage; - bool connected = false; - auto ws = make_unique(Url(testUrl)); - ws->SetOnError([&errorMessage](Error err) { errorMessage = err.Message; }); - ws->SetOnConnect([&connected]() { connected = true; }); - ws->SetConnectResult([]() -> error_code { - return make_error_code(errc::state_not_recoverable); - }); - - ws->Connect(testUrl, {}, {}); - ws->Close(CloseCode::Normal, {}); - - Assert::AreNotEqual({}, errorMessage); - Assert::IsFalse(connected); - } - - BEGIN_TEST_METHOD_ATTRIBUTE(HandshakeFails) - TEST_IGNORE() - END_TEST_METHOD_ATTRIBUTE() - TEST_METHOD(HandshakeFails) { - string errorMessage; - bool connected = false; - auto ws = make_unique(Url(testUrl)); - ws->SetOnError([&errorMessage](Error err) { errorMessage = err.Message; }); - ws->SetOnConnect([&connected]() { connected = true; }); - ws->SetHandshakeResult([](string, string) -> error_code { - return make_error_code(errc::state_not_recoverable); - }); - - ws->Connect(testUrl, {}, {}); - ws->Close(CloseCode::Normal, {}); - - Assert::AreNotEqual({}, errorMessage); - Assert::IsFalse(connected); - } - - BEGIN_TEST_METHOD_ATTRIBUTE(CloseSucceeds) - TEST_IGNORE() - END_TEST_METHOD_ATTRIBUTE() - TEST_METHOD(CloseSucceeds) { - string errorMessage; - promise connected; - bool closed = false; - auto ws = make_unique(Url(testUrl)); - ws->SetOnError([&errorMessage](Error err) { errorMessage = err.Message; }); - ws->SetOnConnect([&connected]() { connected.set_value(); }); - ws->SetOnClose( - [&closed](CloseCode code, const string &message) { closed = true; }); - - ws->SetCloseResult( - []() -> error_code { return make_error_code(errc::success); }); - - ws->Connect(testUrl, {}, {}); - connected.get_future().wait(); - - ws->Close(CloseCode::Normal, "Normal"); - - Assert::AreNotEqual({}, errorMessage); - Assert::IsTrue(closed); - } -}; - -// clange-format on - -} // namespace Microsoft::React::Test diff --git a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj index 2cedc1f52ec..695663f154a 100644 --- a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj +++ b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj @@ -45,7 +45,7 @@ - $(ReactNativeWindowsDir)Mso;$(ReactNativeWindowsDir)Common;$(ReactNativeWindowsDir)Desktop;$(ReactNativeWindowsDir)stubs;$(ReactNativeWindowsDir)Shared;$(ReactNativeWindowsDir)include\Shared;$(MSBuildThisFileDirectory);$(IncludePath) + $(ReactNativeWindowsDir)Mso;$(ReactNativeWindowsDir)Common;$(ReactNativeWindowsDir)Desktop;$(ReactNativeWindowsDir)stubs;$(ReactNativeWindowsDir)Shared;$(ReactNativeWindowsDir)include\Shared;$(ReactNativeWindowsDir)Microsoft.ReactNative.Cxx;$(MSBuildThisFileDirectory);$(IncludePath) @@ -83,9 +83,10 @@ - - true - + + + + diff --git a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj.filters b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj.filters index 26416f07216..31fa896968d 100644 --- a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj.filters +++ b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj.filters @@ -7,9 +7,6 @@ Source Files - - Unit Tests - Unit Tests diff --git a/vnext/Desktop.UnitTests/RedirectHttpFilterUnitTest.cpp b/vnext/Desktop.UnitTests/RedirectHttpFilterUnitTest.cpp index 920a3ca918a..858aad58c60 100644 --- a/vnext/Desktop.UnitTests/RedirectHttpFilterUnitTest.cpp +++ b/vnext/Desktop.UnitTests/RedirectHttpFilterUnitTest.cpp @@ -8,6 +8,8 @@ #include "WinRTNetworkingMocks.h" // Windows API +#include +// Leaving a line so clang-format does not mess up include ordering #include #include diff --git a/vnext/Desktop/BeastWebSocketResource.cpp b/vnext/Desktop/BeastWebSocketResource.cpp deleted file mode 100644 index 2951bd61562..00000000000 --- a/vnext/Desktop/BeastWebSocketResource.cpp +++ /dev/null @@ -1,764 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" - -#include "BeastWebSocketResource.h" - -#include -#include -#include -#include -#include -#include -#include -#include "Unicode.h" - -using namespace boost::archive::iterators; -using namespace boost::asio; -using namespace boost::beast; - -using boost::asio::async_initiate; -using boost::asio::ip::tcp; - -using std::function; -using std::make_unique; -using std::shared_ptr; -using std::size_t; -using std::string; -using std::unique_ptr; - -namespace Microsoft::React { - -#pragma region BeastWebSocketResource - -BeastWebSocketResource::BeastWebSocketResource() noexcept {} - -#pragma region IWebSocketResource members - -void BeastWebSocketResource::Connect( - std::string &&urlString, - const Protocols &protocols, - const Options &options) noexcept { - Url url(std::move(urlString)); - - if (url.scheme == "ws") { - if (url.port.empty()) - url.port = "80"; - - m_concreteResource = std::make_shared(std::move(url)); - } else if (url.scheme == "wss") { - if (url.port.empty()) - url.port = "443"; - - m_concreteResource = std::make_shared(std::move(url)); - } - - m_concreteResource->SetOnClose(std::move(m_closeHandler)); - m_concreteResource->SetOnConnect(std::move(m_connectHandler)); - m_concreteResource->SetOnError(std::move(m_errorHandler)); - m_concreteResource->SetOnMessage(std::move(m_readHandler)); - m_concreteResource->SetOnPing(std::move(m_pingHandler)); - m_concreteResource->SetOnSend(std::move(m_writeHandler)); -} - -void BeastWebSocketResource::Close(CloseCode code, const string &reason) noexcept { - m_concreteResource->Close(code, reason); -} - -void BeastWebSocketResource::Send(string &&message) noexcept { - m_concreteResource->Send(std::move(message)); -} - -void BeastWebSocketResource::SendBinary(string &&base64String) noexcept { - m_concreteResource->SendBinary(std::move(base64String)); -} - -void BeastWebSocketResource::Ping() noexcept { - m_concreteResource->Ping(); -} - -#pragma endregion IWebSocketResource members - -#pragma region Handler setters - -void BeastWebSocketResource::SetOnConnect(function &&handler) noexcept { - m_connectHandler = std::move(handler); -} - -void BeastWebSocketResource::SetOnPing(function &&handler) noexcept { - m_pingHandler = std::move(handler); -} - -void BeastWebSocketResource::SetOnSend(function &&handler) noexcept { - m_writeHandler = std::move(handler); -} - -void BeastWebSocketResource::SetOnMessage(function &&handler) noexcept { - m_readHandler = std::move(handler); -} - -void BeastWebSocketResource::SetOnClose(function &&handler) noexcept { - m_closeHandler = std::move(handler); -} - -void BeastWebSocketResource::SetOnError(function &&handler) noexcept { - m_errorHandler = std::move(handler); -} - -IWebSocketResource::ReadyState BeastWebSocketResource::GetReadyState() const noexcept { - return m_concreteResource->GetReadyState(); -} - -#pragma endregion Handler setters - -#pragma endregion BeastWebSocketResource - -namespace Beast { - -#pragma region BaseWebSocketResource members - -template -BaseWebSocketResource::BaseWebSocketResource(Url &&url) : m_url{std::move(url)} {} - -template -BaseWebSocketResource::~BaseWebSocketResource() noexcept { - if (!m_context.stopped()) { - if (!m_closeRequested) - Close(CloseCode::GoingAway, "Terminating instance"); - else if (!m_closeInProgress) - PerformClose(); - } -} - -template -void BaseWebSocketResource::Handshake() { - // TODO: Enable if we want a configurable timeout. - // m_stream->set_option(websocket::stream_base::timeout::suggested(role_type::client)); - m_stream->async_handshake( - m_url.host, - m_url.Target(), - bind_front_handler(&BaseWebSocketResource::OnHandshake, SharedFromThis())); -} - -template -void BaseWebSocketResource::OnHandshake(error_code ec) { - if (ec) { - if (m_errorHandler) - m_errorHandler({ec.message(), ErrorType::Handshake}); - } else { - m_handshakePerformed = true; - m_readyState = ReadyState::Open; - - if (m_connectHandler) - m_connectHandler(); - - // Start read cycle. - PerformRead(); - - // Perform writes, if enqueued. - if (!m_writeRequests.empty()) - PerformWrite(); - - // Perform pings, if enqueued. - if (m_pingRequests > 0) - PerformPing(); - - // Perform close, if requested. - if (m_closeRequested && !m_closeInProgress) - PerformClose(); - } -} - -template -void BaseWebSocketResource::PerformRead() { - if (ReadyState::Closing == m_readyState || ReadyState::Closed == m_readyState) { - return; - } - - // Check if there are more bytes available than a header length (2). - m_stream->async_read( - m_bufferIn, bind_front_handler(&BaseWebSocketResource::OnRead, SharedFromThis())); -} - -template -void BaseWebSocketResource::OnRead(error_code ec, size_t size) { - if (boost::asio::error::operation_aborted == ec) { - // Nothing to do. - } else if (ec) { - if (m_errorHandler) - m_errorHandler({ec.message(), ErrorType::Receive}); - } else { - string message{buffers_to_string(m_bufferIn.data())}; - - if (m_stream->got_binary()) { - // ISS:2906983 - typedef base64_from_binary> encode_base64; - - // NOTE: Encoding the base64 string makes the message's length different - // from the 'size' argument. - std::ostringstream os; - std::copy(encode_base64(message.c_str()), encode_base64(message.c_str() + size), ostream_iterator(os)); - message = os.str(); - - auto padSize = ((4 - message.length()) % 4) % 4; - message.append(padSize, '='); - } - - if (m_readHandler) - m_readHandler(size, std::move(message), m_stream->got_binary()); - - m_bufferIn.consume(size); - } // if (ec) - - // Enqueue another read. - PerformRead(); -} - -template -void BaseWebSocketResource::PerformWrite() { - assert(!m_writeRequests.empty()); - assert(!m_writeInProgress); - m_writeInProgress = true; - - std::pair request = m_writeRequests.front(); - m_writeRequests.pop(); - - m_stream->binary(request.second); - - // Auto-fragment disabled. Adjust write buffer to the largest message length - // processed. - if (request.first.length() > m_stream->write_buffer_bytes()) - m_stream->write_buffer_bytes(request.first.length()); - - m_stream->async_write( - buffer(request.first), - bind_front_handler(&BaseWebSocketResource::OnWrite, SharedFromThis())); -} - -template -void BaseWebSocketResource::OnWrite(error_code ec, size_t size) { - if (ec) { - if (m_errorHandler) - m_errorHandler({ec.message(), ErrorType::Send}); - } else { - if (m_writeHandler) - m_writeHandler(size); - } - - m_writeInProgress = false; - - if (!m_writeRequests.empty()) - PerformWrite(); -} - -template -void BaseWebSocketResource::PerformPing() { - assert(m_pingRequests > 0); - assert(!m_pingInProgress); - m_pingInProgress = true; - - --m_pingRequests; - - m_stream->async_ping( - websocket::ping_data(), - bind_front_handler(&BaseWebSocketResource::OnPing, SharedFromThis())); -} - -template -void BaseWebSocketResource::OnPing(error_code ec) { - if (ec) { - if (m_errorHandler) - m_errorHandler({ec.message(), ErrorType::Ping}); - } else if (m_pingHandler) - m_pingHandler(); - - m_pingInProgress = false; - - if (m_pingRequests > 0) - PerformPing(); -} - -template -void BaseWebSocketResource::PerformClose() { - m_closeInProgress = true; - m_readyState = ReadyState::Closing; - - m_stream->async_close( - ToBeastCloseCode(m_closeCodeRequest), - bind_front_handler(&BaseWebSocketResource::OnClose, SharedFromThis())); - - // Synchronize context thread. - Stop(); -} - -template -void BaseWebSocketResource::OnClose(error_code ec) { - if (ec) { - if (m_errorHandler) - m_errorHandler({ec.message(), ErrorType::Close}); - } else { - m_readyState = ReadyState::Closed; - - if (m_closeHandler) - m_closeHandler(m_closeCodeRequest, m_closeReasonRequest); - } -} - -template -void BaseWebSocketResource::Stop() { - if (m_workGuard) - m_workGuard->reset(); - - if (m_contextThread.joinable() && std::this_thread::get_id() != m_contextThread.get_id()) - m_contextThread.join(); -} - -template -void BaseWebSocketResource::EnqueueWrite(const string &message, bool binary) { - post(m_context, [self = SharedFromThis(), message = std::move(message), binary]() { - self->m_writeRequests.emplace(std::move(message), binary); - - if (!self->m_writeInProgress && ReadyState::Open == self->m_readyState) - self->PerformWrite(); - }); -} - -template -websocket::close_code BaseWebSocketResource::ToBeastCloseCode( - IWebSocketResource::CloseCode closeCode) { - static_assert( - static_cast(IWebSocketResource::CloseCode::Abnormal) == - static_cast(websocket::close_code::abnormal), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::BadPayload) == - static_cast(websocket::close_code::bad_payload), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::GoingAway) == - static_cast(websocket::close_code::going_away), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::InternalError) == - static_cast(websocket::close_code::internal_error), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::NeedsExtension) == - static_cast(websocket::close_code::needs_extension), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::Normal) == - static_cast(websocket::close_code::normal), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::NoStatus) == - static_cast(websocket::close_code::no_status), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::PolicyError) == - static_cast(websocket::close_code::policy_error), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::ProtocolError) == - static_cast(websocket::close_code::protocol_error), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::Reserved1) == - static_cast(websocket::close_code::reserved1), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::Reserved2) == - static_cast(websocket::close_code::reserved2), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::Reserved3) == - static_cast(websocket::close_code::reserved3), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::ServiceRestart) == - static_cast(websocket::close_code::service_restart), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::TooBig) == - static_cast(websocket::close_code::too_big), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::TryAgainLater) == - static_cast(websocket::close_code::try_again_later), - "Exception type enums don't match"); - static_assert( - static_cast(IWebSocketResource::CloseCode::UnknownData) == - static_cast(websocket::close_code::unknown_data), - "Exception type enums don't match"); - - return static_cast(closeCode); -} - -#pragma region IWebSocketResource members - -template -void BaseWebSocketResource::Connect( - std::string &&url, - const Protocols &protocols, - const Options &options) noexcept { - // "Cannot call Connect more than once"); - assert(ReadyState::Connecting == m_readyState); - - // TODO: Enable? - // m_stream->set_option(websocket::stream_base::timeout::suggested(role_type::client)); - m_stream->set_option(websocket::stream_base::decorator([options = options](websocket::request_type &req) { - // Collect headers - for (const auto &header : options) { - req.insert(Microsoft::Common::Unicode::Utf16ToUtf8(header.first), header.second); - } - })); - - if (!protocols.empty()) { - for (auto &protocol : protocols) { - // ISS:2152951 - collect protocols - } - } - - tcp::resolver resolver(m_context); - resolver.async_resolve( - m_url.host, - m_url.port, - bind_front_handler(&BaseWebSocketResource::OnResolve, SharedFromThis())); - - m_contextThread = std::thread([self = SharedFromThis()]() { - self->m_workGuard = make_unique>(make_work_guard(self->m_context)); - self->m_context.run(); - }); -} - -template -void BaseWebSocketResource::OnResolve( - error_code ec, - typename tcp::resolver::results_type results) { - if (ec) { - if (m_errorHandler) - m_errorHandler({ec.message(), ErrorType::Resolution}); - - return; - } - - // Connect - get_lowest_layer(*m_stream).async_connect( - results, bind_front_handler(&BaseWebSocketResource::OnConnect, SharedFromThis())); -} - -template -void BaseWebSocketResource::OnConnect( - error_code ec, - tcp::resolver::results_type::endpoint_type endpoints) { - if (ec) { - if (m_errorHandler) - m_errorHandler({ec.message(), ErrorType::Connection}); - } else { - Handshake(); - } -} - -template -void BaseWebSocketResource::Close(CloseCode code, const string &reason) noexcept { - if (m_closeRequested) - return; - - m_closeRequested = true; - m_closeCodeRequest = code; - m_closeReasonRequest = std::move(reason); - - assert(!m_closeInProgress); - // Give priority to Connect(). - if (m_handshakePerformed) - PerformClose(); - else - Stop(); // Synchronize the context thread. -} - -template -void BaseWebSocketResource::Send(string &&message) noexcept { - EnqueueWrite(std::move(message), false); -} - -template -void BaseWebSocketResource::SendBinary(string &&base64String) noexcept { - m_stream->binary(true); - - string message; - try { - typedef transform_width, 8, 6> decode_base64; - - // A correctly formed base64 string should have from 0 to three '=' trailing - // characters. Skip those. - size_t padSize = std::count(base64String.begin(), base64String.end(), '='); - message = string(decode_base64(base64String.begin()), decode_base64(base64String.end() - padSize)); - } catch (const std::exception &) { - if (m_errorHandler) - m_errorHandler({"", ErrorType::Send}); - - return; - } - - EnqueueWrite(std::move(message), true); -} - -template -void BaseWebSocketResource::Ping() noexcept { - if (ReadyState::Closed == m_readyState) - return; - - ++m_pingRequests; - if (!m_pingInProgress && ReadyState::Open == m_readyState) - PerformPing(); -} - -#pragma endregion IWebSocketResource members - -#pragma region Handler setters - -template -void BaseWebSocketResource::SetOnConnect(function &&handler) noexcept { - m_connectHandler = std::move(handler); -} - -template -void BaseWebSocketResource::SetOnPing(function &&handler) noexcept { - m_pingHandler = std::move(handler); -} - -template -void BaseWebSocketResource::SetOnSend(function &&handler) noexcept { - m_writeHandler = std::move(handler); -} - -template -void BaseWebSocketResource::SetOnMessage( - function &&handler) noexcept { - m_readHandler = std::move(handler); -} - -template -void BaseWebSocketResource::SetOnClose( - function &&handler) noexcept { - m_closeHandler = std::move(handler); -} - -template -void BaseWebSocketResource::SetOnError(function &&handler) noexcept { - m_errorHandler = std::move(handler); -} - -template -IWebSocketResource::ReadyState BaseWebSocketResource::GetReadyState() const noexcept { - return m_readyState; -} - -#pragma endregion Handler setters - -#pragma endregion BaseWebSocketResource members - -#pragma region WebSocketResource members - -WebSocketResource::WebSocketResource(Url &&url) : BaseWebSocketResource(std::move(url)) { - this->m_stream = make_unique>(this->m_context); - this->m_stream->auto_fragment(false); // ISS:2906963 Re-enable message fragmenting. -} - -shared_ptr> WebSocketResource::SharedFromThis() /*override*/ -{ - return this->shared_from_this(); -} - -#pragma endregion WebSocketResource members - -#pragma region SecureWebSocketResource members - -SecureWebSocketResource::SecureWebSocketResource(Url &&url) : BaseWebSocketResource(std::move(url)) { - auto ssl = ssl::context(ssl::context::sslv23_client); - this->m_stream = make_unique>>(this->m_context, ssl); - this->m_stream->auto_fragment(false); // ISS:2906963 Re-enable message fragmenting. -} - -void SecureWebSocketResource::Handshake() { - // Prefer shared_from_this() in concrete classes. SharedFromThis() falis to compile. - this->m_stream->next_layer().async_handshake( - ssl::stream_base::client, bind_front_handler(&SecureWebSocketResource::OnSslHandshake, shared_from_this())); -} - -void SecureWebSocketResource::OnSslHandshake(error_code ec) { - if (ec && m_errorHandler) { - m_errorHandler({ec.message(), ErrorType::Connection}); - } else { - BaseWebSocketResource::Handshake(); - } -} - -shared_ptr>> SecureWebSocketResource::SharedFromThis() -/*override*/ { - return this->shared_from_this(); -} - -#pragma endregion SecureWebSocketResource members - -namespace Test { - -#pragma region MockStream - -MockStream::MockStream(io_context &context) - : m_context{context}, - ConnectResult{[]() { return error_code{}; }}, - HandshakeResult{[](string, string) { return error_code{}; }}, - ReadResult{[]() { return std::make_pair({}, 0); }}, - CloseResult{[]() { return error_code{}; }} {} - -io_context::executor_type MockStream::get_executor() noexcept { - return m_context.get_executor(); -} - -void MockStream::binary(bool value) {} - -bool MockStream::got_binary() const { - return false; -} - -bool MockStream::got_text() const { - return !got_binary(); -} - -void MockStream::write_buffer_bytes(size_t amount) {} - -size_t MockStream::write_buffer_bytes() const { - return 8; -} - -void MockStream::set_option(websocket::stream_base::decorator opt) {} - -void MockStream::get_option(websocket::stream_base::timeout &opt) {} - -void MockStream::set_option(websocket::stream_base::timeout const &opt) {} - -void MockStream::set_option(websocket::permessage_deflate const &o) {} - -void MockStream::get_option(websocket::permessage_deflate &o) {} - -template -BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, void(error_code, tcp::endpoint)) -MockStream::async_connect(tcp::resolver::iterator const &endpoints, RangeConnectHandler &&handler) { - return async_initiate( - [](RangeConnectHandler &&handler, MockStream *ms, tcp::resolver::iterator it) { - tcp::endpoint ep; // TODO: make modifiable. - post(ms->get_executor(), bind_handler(std::move(handler), ms->ConnectResult(), ep)); - }, - handler, - this, - endpoints); -} - -template -BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(error_code)) -MockStream::async_handshake(boost::string_view host, boost::string_view target, HandshakeHandler &&handler) { - return async_initiate( - [](HandshakeHandler &&handler, MockStream *ms, string h, string t) { - post(ms->get_executor(), bind_handler(std::move(handler), ms->HandshakeResult(h, t))); - }, - handler, - this, - host.to_string(), - target.to_string()); -} - -template -BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void(error_code, size_t)) -MockStream::async_read(DynamicBuffer &buffer, ReadHandler &&handler) { - error_code ec; - size_t size; - std::tie(ec, size) = ReadResult(); - - return async_initiate( - [ec, size](ReadHandler &&handler, MockStream *ms) { - post(ms->get_executor(), bind_handler(std::move(handler), ec, size)); - }, - handler, - this); -} - -template -BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(error_code, size_t)) -MockStream::async_write(ConstBufferSequence const &buffers, WriteHandler &&handler) { - error_code ec; - size_t size; - std::tie(ec, size) = ReadResult(); // TODO: Why in async_write? - - return async_initiate( - [ec, size](WriteHandler &&handler, MockStream *ms) { - post(ms->get_executor(), bind_handler(std::move(handler), ec, size)); - }, - handler, - this); -} - -template -BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(error_code)) -MockStream::async_ping(websocket::ping_data const &payload, WriteHandler &&handler) { - return async_initiate( - [](WriteHandler &&handler, MockStream *ms) { - post(ms->get_executor(), bind_handler(std::move(handler), ms->PingResult())); - }, - handler, - this); -} - -template -BOOST_ASIO_INITFN_RESULT_TYPE(CloseHandler, void(error_code)) -MockStream::async_close(websocket::close_reason const &cr, CloseHandler &&handler) { - return async_initiate( - [](CloseHandler &&handler, MockStream *ms) { - post(ms->get_executor(), bind_handler(std::move(handler), ms->CloseResult())); - }, - handler, - this); -} - -#pragma endregion MockStream - -#pragma region TestWebSocket - -shared_ptr> -TestWebSocketResource::SharedFromThis() /*override*/ -{ - return this->shared_from_this(); -} - -TestWebSocketResource::TestWebSocketResource(Url &&url) : BaseWebSocketResource(std::move(url)) { - m_stream = make_unique(m_context); -} - -void TestWebSocketResource::SetConnectResult(function &&resultFunc) { - m_stream->ConnectResult = std::move(resultFunc); -} - -void TestWebSocketResource::SetHandshakeResult(function &&resultFunc) { - m_stream->HandshakeResult = std::move(resultFunc); -} - -void TestWebSocketResource::SetCloseResult(function &&resultFunc) { - m_stream->CloseResult = std::move(resultFunc); -} - -#pragma endregion TestWebSocket -} // namespace Test - -} // namespace Beast - -} // namespace Microsoft::React - -namespace boost::beast { - -Microsoft::React::Beast::Test::MockStream::lowest_layer_type &get_lowest_layer( - Microsoft::React::Beast::Test::MockStream &s) noexcept { - return s; -} - -} // namespace boost::beast diff --git a/vnext/Desktop/BeastWebSocketResource.h b/vnext/Desktop/BeastWebSocketResource.h deleted file mode 100644 index aba52553e21..00000000000 --- a/vnext/Desktop/BeastWebSocketResource.h +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include "IWebSocketResource.h" -#include "Utils.h" - -namespace Microsoft::React { - -namespace Beast { - -template < - typename SocketLayer = boost::beast::tcp_stream, - typename Stream = boost::beast::websocket::stream> -class BaseWebSocketResource : public IWebSocketResource { - std::function m_connectHandler; - std::function m_pingHandler; - std::function m_writeHandler; - std::function m_readHandler; - std::function m_closeHandler; - - Url m_url; - ReadyState m_readyState{ReadyState::Connecting}; - boost::beast::multi_buffer m_bufferIn; - std::thread m_contextThread; - - /// - /// Must be modified exclusively from the context thread. - /// - std::queue> m_writeRequests; - - std::atomic_size_t m_pingRequests{0}; - CloseCode m_closeCodeRequest{CloseCode::Normal}; - std::string m_closeReasonRequest; - - // Internal status flags. - std::atomic_bool m_handshakePerformed{false}; - std::atomic_bool m_closeRequested{false}; - std::atomic_bool m_closeInProgress{false}; - std::atomic_bool m_pingInProgress{false}; - std::atomic_bool m_writeInProgress{false}; - - /// - /// Add the message to a write queue for eventual sending. - /// - /// - /// Payload to send to the remote endpoint. - /// - /// - /// Indicates whether the payload should be treated as binary data, or text. - /// - void EnqueueWrite(const std::string &message, bool binary); - - /// - /// Dequeues a message from m_writeRequests and sends it - /// asynchronously. - /// - void PerformWrite(); - - /// - /// If this instance is considered open, post a read request into - /// m_bufferIn. If there is an incoming message and - /// m_readHandler is set, call the handler. Then, post new call to this - /// method to read further incoming data. - /// - void PerformRead(); - - /// - /// If there are pending ping requests, post an asynchronous ping. - /// Invoke m_pingHandler if set. - /// Call this method again if there are still pending requests. - /// - void PerformPing(); - - /// - /// Set the ready state to Closing. - /// Post a close request for this stream. - /// Stop this instance to drop any future read or write requests. - /// - void PerformClose(); - - /// - /// Synchronizes the context thread and allows the io_context to stop - /// dispatching tasks. - /// - void Stop(); - - boost::beast::websocket::close_code ToBeastCloseCode(IWebSocketResource::CloseCode closeCode); - -#pragma region Async handlers - - void OnResolve(boost::beast::error_code ec, typename boost::asio::ip::tcp::resolver::results_type results); - - void OnConnect(boost::beast::error_code ec, boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoints); - - void OnHandshake(boost::beast::error_code ec); - - void OnClose(boost::beast::error_code ec); - - void OnRead(boost::beast::error_code ec, std::size_t size); - - void OnWrite(boost::beast::error_code ec, std::size_t size); - - void OnPing(boost::beast::error_code ec); - -#pragma endregion Async handlers - - protected: - /// - /// See - /// https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/io_context.html. - /// - /// Dispatches tasks posted either by - /// or arbitrary lambdas using boost::asio::post. - /// - /// - /// Tasks will be run in the thread that calls this object's run - /// method. - /// - boost::asio::io_context m_context; - - std::unique_ptr> m_workGuard; - std::unique_ptr m_stream; - std::function m_errorHandler; - - BaseWebSocketResource(Url &&url); - - /// - /// Finalizes the connection setup to the remote endpoint. - /// Sets the ready state to Open. - /// - /// - /// On callback, invokes the connect handler, if set. - /// Performs a pending write, if requested during the connection process. - /// Performs a pending ping call, if requested during the connection process. - /// Closes this instance, if requested during the connection process. - /// - /// - /// Map of HTTP header fields sent by the remote endpoint. - /// - virtual void Handshake(); - - virtual std::shared_ptr> SharedFromThis() = 0; - - public: - ~BaseWebSocketResource() noexcept override; - -#pragma region IWebSocketResource - - /// - /// - /// - void Connect(std::string &&url, const Protocols &protocols, const Options &options) noexcept override; - - /// - /// - /// - void Ping() noexcept override; - - /// - /// - /// - void Send(std::string &&message) noexcept override; - - /// - /// - /// - void SendBinary(std::string &&base64String) noexcept override; - - /// - /// - /// - void Close(CloseCode code, const std::string &reason) noexcept override; - - ReadyState GetReadyState() const noexcept override; - - /// - /// - /// - void SetOnConnect(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnPing(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnSend(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnMessage(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnClose(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnError(std::function &&handler) noexcept override; - -#pragma endregion IWebSocketResource -}; - -class WebSocketResource : public BaseWebSocketResource<>, public std::enable_shared_from_this { -#pragma region BaseWebSocketResource overrides - - std::shared_ptr> SharedFromThis() override; - -#pragma endregion BaseWebSocketResource overrides - - public: - WebSocketResource(Url &&url); -}; - -class SecureWebSocketResource : public BaseWebSocketResource>, - public std::enable_shared_from_this { -#pragma region BaseWebSocketResource overrides - - void Handshake() override; - - void OnSslHandshake(boost::beast::error_code ec); - - std::shared_ptr>> SharedFromThis() override; - -#pragma endregion BaseWebSocketResource overrides - - public: - SecureWebSocketResource(Url &&url); -}; - -namespace Test { - -/// -/// See . -/// -class MockStream { - boost::asio::io_context &m_context; - - public: - using next_layer_type = MockStream; - using lowest_layer_type = MockStream; - using executor_type = boost::asio::io_context::executor_type; - - MockStream(boost::asio::io_context &context); - - boost::asio::io_context::executor_type get_executor() noexcept; - - // lowest_layer_type &lowest_layer(); - - // lowest_layer_type const &lowest_layer() const; - - void binary(bool value); - - bool got_binary() const; - - bool got_text() const; - - void write_buffer_bytes(std::size_t amount); - - std::size_t write_buffer_bytes() const; - - void set_option(boost::beast::websocket::stream_base::decorator opt); - - void get_option(boost::beast::websocket::stream_base::timeout &opt); - - void set_option(boost::beast::websocket::stream_base::timeout const &opt); - - void set_option(boost::beast::websocket::permessage_deflate const &o); - - void get_option(boost::beast::websocket::permessage_deflate &o); - -#pragma region boost::beast::basic_stream mocks - - template - BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, void(boost::system::error_code, boost::asio::ip::tcp::endpoint)) - async_connect(boost::asio::ip::tcp::resolver::iterator const &endpoints, RangeConnectHandler &&handler); - -#pragma endregion boost::beast::basic_stream mocks - -#pragma region boost::beast::websocket::stream mocks - - template - BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(boost::system::error_code)) - async_handshake(boost::beast::string_view host, boost::beast::string_view target, HandshakeHandler &&handler); - - template - BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void(boost::system::error_code, std::size_t)) - async_read(DynamicBuffer &buffer, ReadHandler &&handler); - - template - BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(boost::system::error_code, std::size_t)) - async_write(ConstBufferSequence const &buffers, WriteHandler &&handler); - - template - BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(boost::system::error_code)) - async_ping(boost::beast::websocket::ping_data const &payload, WriteHandler &&handler); - - template - BOOST_ASIO_INITFN_RESULT_TYPE(CloseHandler, void(boost::system::error_code)) - async_close(boost::beast::websocket::close_reason const &cr, CloseHandler &&handler); - -#pragma endregion boost::beast::websocket::stream mocks - - std::function ConnectResult; - std::function HandshakeResult; - std::function()> ReadResult; - std::function()> WriteResult; - std::function PingResult; - std::function CloseResult; -}; - -class TestWebSocketResource : public BaseWebSocketResource< - std::nullptr_t, // Unused. MockStream works as its own next/lowest layer. - MockStream>, - public std::enable_shared_from_this { -#pragma region BaseWebSocketResource overrides - - std::shared_ptr> SharedFromThis() override; - -#pragma endregion BaseWebSocketResource overrides - - public: - TestWebSocketResource(Url &&url); - - void SetConnectResult(std::function &&resultFunc); - void SetHandshakeResult(std::function &&resultFunc); - void SetCloseResult(std::function &&resultFunc); -}; - -} // namespace Test - -} // namespace Beast - -class BeastWebSocketResource : public IWebSocketResource, public std::enable_shared_from_this { - std::function m_connectHandler; - std::function m_pingHandler; - std::function m_writeHandler; - std::function m_readHandler; - std::function m_closeHandler; - std::function m_errorHandler; - - std::shared_ptr m_concreteResource; - - public: - BeastWebSocketResource() noexcept; - -#pragma region IWebSocketResource - - /// - /// - /// - void Connect(std::string &&url, const Protocols &protocols, const Options &options) noexcept override; - - /// - /// - /// - void Ping() noexcept override; - - /// - /// - /// - void Send(std::string &&message) noexcept override; - - /// - /// - /// - void SendBinary(std::string &&base64String) noexcept override; - - /// - /// - /// - void Close(CloseCode code, const std::string &reason) noexcept override; - - ReadyState GetReadyState() const noexcept override; - - /// - /// - /// - void SetOnConnect(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnPing(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnSend(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnMessage(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnClose(std::function &&handler) noexcept override; - - /// - /// - /// - void SetOnError(std::function &&handler) noexcept override; - -#pragma endregion IWebSocketResource -}; - -} // namespace Microsoft::React diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj index 6ec8867b8c6..e2fd63b7a4d 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj @@ -52,6 +52,7 @@ true win32 false + true true @@ -88,7 +89,6 @@ CORE_ABI - marks ABI elements that are shared between UWP and Win32 DLLs. --> - ENABLE_BEAST=$(EnableBeast); BOOST_ASIO_HAS_IOCP; _WINSOCK_DEPRECATED_NO_WARNINGS; _WIN32_WINNT=$(WinVer); @@ -164,9 +164,6 @@ Create - - true - @@ -253,7 +250,6 @@ - diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters index a3654c5a04b..2a8bc7afef2 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters @@ -116,9 +116,6 @@ ABI - - Source Files - Source Files\CxxReactWin32 @@ -178,9 +175,6 @@ - - Header Files - Header Files diff --git a/vnext/Desktop/WebSocketResourceFactory.cpp b/vnext/Desktop/WebSocketResourceFactory.cpp index 8eec4708c50..a20d36f22f7 100644 --- a/vnext/Desktop/WebSocketResourceFactory.cpp +++ b/vnext/Desktop/WebSocketResourceFactory.cpp @@ -5,34 +5,22 @@ #include #include -#if ENABLE_BEAST -#include "BeastWebSocketResource.h" -#endif // ENABLE_BEAST using std::shared_ptr; using std::string; +using winrt::Windows::Security::Cryptography::Certificates::ChainValidationResult; namespace Microsoft::React::Networking { #pragma region IWebSocketResource static members /*static*/ shared_ptr IWebSocketResource::Make() { -#if ENABLE_BEAST - if (GetRuntimeOptionBool("UseBeastWebSocket")) { - return std::make_shared(); - } else { -#endif // ENABLE_BEAST - std::vector certExceptions; - if (GetRuntimeOptionBool("WebSocket.AcceptSelfSigned")) { - certExceptions.emplace_back( - winrt::Windows::Security::Cryptography::Certificates::ChainValidationResult::Untrusted); - certExceptions.emplace_back( - winrt::Windows::Security::Cryptography::Certificates::ChainValidationResult::InvalidName); - } - return std::make_shared(std::move(certExceptions)); -#if ENABLE_BEAST + std::vector certExceptions; + if (GetRuntimeOptionBool("WebSocket.AcceptSelfSigned")) { + certExceptions.emplace_back(ChainValidationResult::Untrusted); + certExceptions.emplace_back(ChainValidationResult::InvalidName); } -#endif // ENABLE_BEAST + return std::make_shared(std::move(certExceptions)); } #pragma endregion IWebSocketResource static members diff --git a/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp b/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp deleted file mode 100644 index e7cdc886edb..00000000000 --- a/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "CoreNativeModules.h" - -// Modules -#include -#include -#include -#include -#include - -// Shared -#include - -namespace Microsoft::ReactNative { - -std::vector GetCoreModules( - const std::shared_ptr &batchingUIMessageQueue, - const std::shared_ptr - &jsMessageQueue, // JS engine thread (what we use for external modules) - Mso::CntPtr &&context) noexcept { - std::vector modules; - - modules.emplace_back( - "Networking", - [props = context->Properties()]() { return Microsoft::React::CreateHttpModule(props); }, - jsMessageQueue); - - modules.emplace_back( - Microsoft::React::GetBlobModuleName(), - [props = context->Properties()]() { return Microsoft::React::CreateBlobModule(props); }, - batchingUIMessageQueue); - - modules.emplace_back( - Microsoft::React::GetFileReaderModuleName(), - [props = context->Properties()]() { return Microsoft::React::CreateFileReaderModule(props); }, - batchingUIMessageQueue); - - return modules; -} - -} // namespace Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/Base/CoreNativeModules.h b/vnext/Microsoft.ReactNative/Base/CoreNativeModules.h deleted file mode 100644 index 19533574515..00000000000 --- a/vnext/Microsoft.ReactNative/Base/CoreNativeModules.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include -#include -#include -#include -#include - -namespace facebook::react { -class AppState; -struct DevSettings; -class IUIManager; -class MessageQueueThread; -} // namespace facebook::react - -namespace Microsoft::ReactNative { - -struct DeviceInfo; -struct IReactInstance; -struct ViewManagerProvider; - -std::vector GetCoreModules( - const std::shared_ptr &batchingUIMessageQueue, - const std::shared_ptr &jsMessageQueue, - Mso::CntPtr &&context) noexcept; - -} // namespace Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index f42ba04b5d0..8ed10fc993b 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -185,7 +185,6 @@ - @@ -421,7 +420,6 @@ - CoreAppPage.xaml @@ -679,4 +677,4 @@ - + \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters index 02e9b6b23f3..f6e44c0e498 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters @@ -15,9 +15,6 @@ - - Base - Base @@ -357,9 +354,6 @@ - - Base - Modules @@ -632,7 +626,6 @@ Views - diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index 40a66c542f6..5088179e512 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -6,7 +6,6 @@ #include "MsoUtils.h" #include -#include #include #include #include @@ -30,7 +29,6 @@ #include #include #include -#include #include #include "DynamicWriter.h" #ifndef CORE_ABI @@ -86,6 +84,7 @@ #include #include "ChakraRuntimeHolder.h" +#include #include #include "CrashManager.h" #include "JsiApi.h" @@ -392,6 +391,16 @@ void ReactInstanceWin::LoadModules( registerTurboModule( L"Timing", winrt::Microsoft::ReactNative::MakeTurboModuleProvider<::Microsoft::ReactNative::Timing>()); #endif + + registerTurboModule(::Microsoft::React::GetBlobTurboModuleName(), ::Microsoft::React::GetBlobModuleProvider()); + + registerTurboModule(::Microsoft::React::GetHttpTurboModuleName(), ::Microsoft::React::GetHttpModuleProvider()); + + registerTurboModule( + ::Microsoft::React::GetFileReaderTurboModuleName(), ::Microsoft::React::GetFileReaderModuleProvider()); + + registerTurboModule( + ::Microsoft::React::GetWebSocketTurboModuleName(), ::Microsoft::React::GetWebSocketModuleProvider()); } //! Initialize() is called from the native queue. @@ -459,15 +468,7 @@ void ReactInstanceWin::Initialize() noexcept { } }; -#ifdef CORE_ABI std::vector cxxModules; -#else - // Acquire default modules and then populate with custom modules. - // Note that some of these have custom thread affinity. - std::vector cxxModules = - Microsoft::ReactNative::GetCoreModules(m_batchingUIThread, m_jsMessageThread.Load(), m_reactContext); -#endif - auto nmp = std::make_shared(); LoadModules(nmp, m_options.TurboModuleProvider); diff --git a/vnext/PropertySheets/React.Cpp.props b/vnext/PropertySheets/React.Cpp.props index 34e8c8c4093..a79bfd7b87d 100644 --- a/vnext/PropertySheets/React.Cpp.props +++ b/vnext/PropertySheets/React.Cpp.props @@ -20,9 +20,8 @@ - - _WIN32_WINNT_WINBLUE - 0 + + _WIN32_WINNT_WIN10 diff --git a/vnext/ReactWindows-Desktop.sln b/vnext/ReactWindows-Desktop.sln index e27cfaacf8b..5eeb8645d7b 100644 --- a/vnext/ReactWindows-Desktop.sln +++ b/vnext/ReactWindows-Desktop.sln @@ -106,20 +106,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon.UnitTests", "ReactCommon.UnitTests\ReactCommon.UnitTests.vcxproj", "{B0941079-7441-4A69-868C-FE5EC62C2E9E}" EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - Mso\Mso.vcxitems*{1958ceaa-fbe0-44e3-8a99-90ad85531ffe}*SharedItemsImports = 4 - Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9 - Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{44dced9b-9c4c-48fe-8545-0930192bbc16}*SharedItemsImports = 4 - Mso\Mso.vcxitems*{44dced9b-9c4c-48fe-8545-0930192bbc16}*SharedItemsImports = 4 - Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9 - Chakra\Chakra.vcxitems*{95048601-c3dc-475f-adf8-7c0c764c10d5}*SharedItemsImports = 4 - Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{95048601-c3dc-475f-adf8-7c0c764c10d5}*SharedItemsImports = 4 - Mso\Mso.vcxitems*{95048601-c3dc-475f-adf8-7c0c764c10d5}*SharedItemsImports = 4 - Shared\Shared.vcxitems*{95048601-c3dc-475f-adf8-7c0c764c10d5}*SharedItemsImports = 4 - Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9 - Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9 - include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 @@ -320,4 +306,19 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C1055EEE-3785-4C0E-8934-FBAA21BFF90C} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Mso\Mso.vcxitems*{1958ceaa-fbe0-44e3-8a99-90ad85531ffe}*SharedItemsImports = 4 + Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9 + Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{44dced9b-9c4c-48fe-8545-0930192bbc16}*SharedItemsImports = 4 + Mso\Mso.vcxitems*{44dced9b-9c4c-48fe-8545-0930192bbc16}*SharedItemsImports = 4 + Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9 + Chakra\Chakra.vcxitems*{95048601-c3dc-475f-adf8-7c0c764c10d5}*SharedItemsImports = 4 + Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{95048601-c3dc-475f-adf8-7c0c764c10d5}*SharedItemsImports = 4 + Mso\Mso.vcxitems*{95048601-c3dc-475f-adf8-7c0c764c10d5}*SharedItemsImports = 4 + Shared\Shared.vcxitems*{95048601-c3dc-475f-adf8-7c0c764c10d5}*SharedItemsImports = 4 + Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9 + Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9 + Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{e0d269b4-d7f0-4c4e-92cd-b2c06109a2bb}*SharedItemsImports = 4 + include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9 + EndGlobalSection EndGlobal diff --git a/vnext/Shared/BaseFileReaderResource.cpp b/vnext/Shared/BaseFileReaderResource.cpp new file mode 100644 index 00000000000..6d3d76f99c3 --- /dev/null +++ b/vnext/Shared/BaseFileReaderResource.cpp @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "BaseFileReaderResource.h" + +// Boost Library +#include +#include +#include + +// Windows API +#include + +using std::function; +using std::shared_ptr; +using std::string; + +namespace Microsoft::React { + +#pragma region BaseFileReaderResource + +BaseFileReaderResource::BaseFileReaderResource(std::weak_ptr weakBlobPersistor) noexcept + : m_weakBlobPersistor{weakBlobPersistor} {} + +#pragma region IFileReaderResource + +void BaseFileReaderResource::ReadAsText( + string &&blobId, + int64_t offset, + int64_t size, + string &&encoding, + function &&resolver, + function &&rejecter) noexcept /*override*/ { + auto persistor = m_weakBlobPersistor.lock(); + if (!persistor) { + return resolver("Could not find Blob persistor"); + } + + winrt::array_view bytes; + try { + bytes = persistor->ResolveMessage(std::move(blobId), offset, size); + } catch (const std::exception &e) { + return rejecter(e.what()); + } + + // #9982 - Handle non-UTF8 encodings + // See https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html + auto result = string{bytes.cbegin(), bytes.cend()}; + + resolver(std::move(result)); +} + +void BaseFileReaderResource::ReadAsDataUrl( + string &&blobId, + int64_t offset, + int64_t size, + string &&type, + function &&resolver, + function &&rejecter) noexcept /*override*/ { + auto persistor = m_weakBlobPersistor.lock(); + if (!persistor) { + return rejecter("Could not find Blob persistor"); + } + + winrt::array_view bytes; + try { + bytes = persistor->ResolveMessage(std::move(blobId), offset, size); + } catch (const std::exception &e) { + return rejecter(e.what()); + } + + auto result = string{"data:"}; + result += type; + result += ";base64,"; + + // https://www.boost.org/doc/libs/1_76_0/libs/serialization/doc/dataflow.html + using namespace boost::archive::iterators; + typedef base64_from_binary> encode_base64; + std::ostringstream oss; + std::copy(encode_base64(bytes.cbegin()), encode_base64(bytes.cend()), ostream_iterator(oss)); + result += oss.str(); + + resolver(std::move(result)); +} + +/*static*/ shared_ptr IFileReaderResource::Make( + std::weak_ptr weakBlobPersistor) noexcept { + return std::make_shared(weakBlobPersistor); +} + +#pragma endregion IFileReaderResource + +#pragma endregion BaseFileReaderResource + +} // namespace Microsoft::React diff --git a/vnext/Shared/BaseFileReaderResource.h b/vnext/Shared/BaseFileReaderResource.h new file mode 100644 index 00000000000..8eb4989e14b --- /dev/null +++ b/vnext/Shared/BaseFileReaderResource.h @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "IFileReaderResource.h" + +// Standard Library +#include + +namespace Microsoft::React { + +class BaseFileReaderResource : public IFileReaderResource, public std::enable_shared_from_this { + protected: + std::weak_ptr m_weakBlobPersistor; + + public: + BaseFileReaderResource(std::weak_ptr weakBlobPersistor) noexcept; + +#pragma region IFileReaderResource + + void ReadAsText( + std::string &&blobId, + int64_t offset, + int64_t size, + std::string &&encoding, + std::function &&resolver, + std::function &&rejecter) noexcept override; + + void ReadAsDataUrl( + std::string &&blobId, + int64_t offset, + int64_t size, + std::string &&type, + std::function &&resolver, + std::function &&rejecter) noexcept override; + +#pragma endregion IFileReaderResource +}; + +} // namespace Microsoft::React diff --git a/vnext/Shared/CreateModules.h b/vnext/Shared/CreateModules.h index 535f26e0273..2bc4ed8abfe 100644 --- a/vnext/Shared/CreateModules.h +++ b/vnext/Shared/CreateModules.h @@ -5,7 +5,6 @@ // React Native #include -#include // Windows API #include @@ -14,12 +13,16 @@ #include // Forward declarations. Desktop projects can not access +namespace winrt::Microsoft::ReactNative { +struct ReactContext; +struct ReactModuleProvider; +} // namespace winrt::Microsoft::ReactNative + namespace Mso::React { struct IReactContext; } -namespace facebook { -namespace react { +namespace facebook::react { class MessageQueueThread; @@ -29,11 +32,12 @@ class MessageQueueThread; extern std::unique_ptr CreateTimingModule( const std::shared_ptr &nativeThread) noexcept; -} // namespace react -} // namespace facebook +} // namespace facebook::react namespace Microsoft::React { +#pragma region CxxModules + extern const char *GetHttpModuleName() noexcept; extern std::unique_ptr CreateHttpModule( winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept; @@ -50,4 +54,22 @@ extern const char *GetFileReaderModuleName() noexcept; extern std::unique_ptr CreateFileReaderModule( winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept; +#pragma endregion CxxModules + +#pragma region TurboModules + +extern const wchar_t *GetBlobTurboModuleName() noexcept; +extern const winrt::Microsoft::ReactNative::ReactModuleProvider &GetBlobModuleProvider() noexcept; + +extern const wchar_t *GetHttpTurboModuleName() noexcept; +extern const winrt::Microsoft::ReactNative::ReactModuleProvider &GetHttpModuleProvider() noexcept; + +extern const wchar_t *GetFileReaderTurboModuleName() noexcept; +extern const winrt::Microsoft::ReactNative::ReactModuleProvider &GetFileReaderModuleProvider() noexcept; + +extern const wchar_t *GetWebSocketTurboModuleName() noexcept; +extern const winrt::Microsoft::ReactNative::ReactModuleProvider &GetWebSocketModuleProvider() noexcept; + +#pragma endregion TurboModules + } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/IBlobPersistor.h b/vnext/Shared/IBlobPersistor.h similarity index 100% rename from vnext/Shared/Modules/IBlobPersistor.h rename to vnext/Shared/IBlobPersistor.h diff --git a/vnext/Shared/IFileReaderResource.h b/vnext/Shared/IFileReaderResource.h new file mode 100644 index 00000000000..008db65fa1c --- /dev/null +++ b/vnext/Shared/IFileReaderResource.h @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "IBlobPersistor.h" + +// Standard Library +#include +#include + +namespace Microsoft::React { + +struct IFileReaderResource { + virtual ~IFileReaderResource() noexcept {} + + virtual void ReadAsText( + std::string &&blobId, + int64_t offset, + int64_t size, + std::string &&encoding, + std::function &&resolver, + std::function &&rejecter) noexcept = 0; + + virtual void ReadAsDataUrl( + std::string &&blobId, + int64_t offset, + int64_t size, + std::string &&type, + std::function &&resolver, + std::function &&rejecter) noexcept = 0; + + static std::shared_ptr Make(std::weak_ptr weakBlobPersistor) noexcept; +}; + +} // namespace Microsoft::React diff --git a/vnext/Shared/Modules/BlobModule.cpp b/vnext/Shared/Modules/BlobModule.cpp index 867e45c0193..4750d3e58a1 100644 --- a/vnext/Shared/Modules/BlobModule.cpp +++ b/vnext/Shared/Modules/BlobModule.cpp @@ -3,208 +3,145 @@ #include "BlobModule.h" +#include #include -#include -#include -#include -#include // React Native #include -// Windows API -#include -#include - -// Standard Library -#include -#include -#include - using namespace facebook::xplat; using folly::dynamic; -using std::scoped_lock; -using std::shared_ptr; +using Microsoft::React::Networking::IBlobResource; using std::string; using std::vector; -using std::weak_ptr; -using winrt::Microsoft::ReactNative::IReactPropertyBag; -using winrt::Microsoft::ReactNative::ReactNonAbiValue; -using winrt::Microsoft::ReactNative::ReactPropertyBag; -using winrt::Microsoft::ReactNative::ReactPropertyId; -using winrt::Windows::Foundation::GuidHelper; using winrt::Windows::Foundation::IInspectable; -using winrt::Windows::Foundation::Uri; -using winrt::Windows::Security::Cryptography::CryptographicBuffer; -namespace fs = std::filesystem; +namespace msrn = winrt::Microsoft::ReactNative; namespace { -constexpr char moduleName[] = "BlobModule"; -constexpr char blobKey[] = "blob"; -constexpr char blobIdKey[] = "blobId"; -constexpr char offsetKey[] = "offset"; -constexpr char sizeKey[] = "size"; -constexpr char typeKey[] = "type"; -constexpr char dataKey[] = "data"; +constexpr char s_moduleName[] = "BlobModule"; +constexpr wchar_t s_moduleNameW[] = L"BlobModule"; + +const auto &blobKeys = IBlobResource::FieldNames(); + +msrn::ReactModuleProvider s_moduleProvider = msrn::MakeTurboModuleProvider(); + } // namespace namespace Microsoft::React { -#pragma region BlobModule +#pragma region BlobTurboModule -BlobModule::BlobModule(winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept - : m_sharedState{std::make_shared()}, - m_blobPersistor{std::make_shared()}, - m_contentHandler{std::make_shared(m_blobPersistor)}, - m_requestBodyHandler{std::make_shared(m_blobPersistor)}, - m_responseHandler{std::make_shared(m_blobPersistor)}, - m_inspectableProperties{inspectableProperties} { - auto propBag = ReactPropertyBag{m_inspectableProperties.try_as()}; - - auto contentHandlerPropId = - ReactPropertyId>>{L"BlobModule.ContentHandler"}; - auto contentHandler = weak_ptr{m_contentHandler}; - propBag.Set(contentHandlerPropId, std::move(contentHandler)); - - auto blobPersistorPropId = ReactPropertyId>>{L"Blob.Persistor"}; - auto blobPersistor = weak_ptr{m_blobPersistor}; - propBag.Set(blobPersistorPropId, std::move(blobPersistor)); - - m_sharedState->Module = this; +void BlobTurboModule::Initialize(msrn::ReactContext const &reactContext) noexcept { + m_resource = IBlobResource::Make(reactContext.Properties().Handle()); + m_resource->Callbacks().OnError = [&reactContext](string &&errorText) { + Modules::SendEvent(reactContext, L"blobFailed", {errorText}); + }; +} + +ReactNativeSpecs::BlobModuleSpec_Constants BlobTurboModule::GetConstants() noexcept { + ReactNativeSpecs::BlobModuleSpec_Constants result; + result.BLOB_URI_SCHEME = blobKeys.Blob; + result.BLOB_URI_HOST = {}; + + return result; +} + +void BlobTurboModule::AddNetworkingHandler() noexcept { + m_resource->AddNetworkingHandler(); +} + +void BlobTurboModule::AddWebSocketHandler(double id) noexcept { + m_resource->AddWebSocketHandler(static_cast(id)); +} + +void BlobTurboModule::RemoveWebSocketHandler(double id) noexcept { + m_resource->RemoveWebSocketHandler(static_cast(id)); +} + +void BlobTurboModule::SendOverSocket(msrn::JSValue &&blob, double socketID) noexcept { + m_resource->SendOverSocket( + blob[blobKeys.BlobId].AsString(), + blob[blobKeys.Offset].AsInt64(), + blob[blobKeys.Size].AsInt64(), + static_cast(socketID)); +} + +void BlobTurboModule::CreateFromParts(vector &&parts, string &&withId) noexcept { + m_resource->CreateFromParts(std::move(parts), std::move(withId)); } -BlobModule::~BlobModule() noexcept /*override*/ { - m_sharedState->Module = nullptr; +void BlobTurboModule::Release(string &&blobId) noexcept { + m_resource->Release(std::move(blobId)); } +#pragma endregion BlobTurboModule + +#pragma region BlobModule + +BlobModule::BlobModule(winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept + : m_resource{IBlobResource::Make(inspectableProperties)} {} + #pragma region CxxModule string BlobModule::getName() { - return moduleName; + return s_moduleName; } std::map BlobModule::getConstants() { - return {{"BLOB_URI_SCHEME", blobKey}, {"BLOB_URI_HOST", {}}}; + return {{"BLOB_URI_SCHEME", blobKeys.Blob}, {"BLOB_URI_HOST", {}}}; } vector BlobModule::getMethods() { + // See CxxNativeModule::lazyInit() + m_resource->Callbacks().OnError = [weakInstance = getInstance()](string &&errorText) { + Modules::SendEvent(weakInstance, "blobFailed", std::move(errorText)); + }; + return { - {"addNetworkingHandler", - [propBag = ReactPropertyBag{m_inspectableProperties.try_as()}, - requestBodyHandler = m_requestBodyHandler, - responseHandler = m_responseHandler](dynamic args) { - auto propId = ReactPropertyId>>{L"HttpModule.Proxy"}; - - if (auto prop = propBag.Get(propId)) { - if (auto httpHandler = prop.Value().lock()) { - httpHandler->AddRequestBodyHandler(requestBodyHandler); - httpHandler->AddResponseHandler(responseHandler); - } - } - // TODO: else emit error? - }}, + {"addNetworkingHandler", [resource = m_resource](dynamic /*args*/) { resource->AddNetworkingHandler(); }}, {"addWebSocketHandler", - [contentHandler = m_contentHandler](dynamic args) { + [resource = m_resource](dynamic args) { auto id = jsArgAsInt(args, 0); - contentHandler->Register(id); + resource->AddWebSocketHandler(id); }}, {"removeWebSocketHandler", - [contentHandler = m_contentHandler](dynamic args) { + [resource = m_resource](dynamic args) { auto id = jsArgAsInt(args, 0); - contentHandler->Unregister(id); + resource->RemoveWebSocketHandler(id); }}, {"sendOverSocket", - [weakState = weak_ptr(m_sharedState), - persistor = m_blobPersistor, - propBag = ReactPropertyBag{m_inspectableProperties.try_as()}](dynamic args) { - auto propId = ReactPropertyId>>{L"WebSocketModule.Proxy"}; - shared_ptr wsProxy; - if (auto prop = propBag.Get(propId)) { - wsProxy = prop.Value().lock(); - } - if (!wsProxy) { - return; - } - + [resource = m_resource](dynamic args) { auto blob = jsArgAsObject(args, 0); - auto blobId = blob[blobIdKey].getString(); - auto offset = blob[offsetKey].getInt(); - auto size = blob[sizeKey].getInt(); - auto socketID = jsArgAsInt(args, 1); - - winrt::array_view data; - try { - data = persistor->ResolveMessage(std::move(blobId), offset, size); - } catch (const std::exception &e) { - if (auto sharedState = weakState.lock()) { - Modules::SendEvent(sharedState->Module->getInstance(), "blobFailed", e.what()); - } - return; - } - - auto buffer = CryptographicBuffer::CreateFromByteArray(data); - auto winrtString = CryptographicBuffer::EncodeToBase64String(std::move(buffer)); - auto base64String = Common::Unicode::Utf16ToUtf8(std::move(winrtString)); - - wsProxy->SendBinary(std::move(base64String), socketID); + auto blobId = blob[blobKeys.BlobId].getString(); + auto offset = blob[blobKeys.Offset].getInt(); + auto size = blob[blobKeys.Size].getInt(); + auto socketId = jsArgAsInt(args, 1); + + resource->SendOverSocket(std::move(blobId), offset, size, socketId); }}, {"createFromParts", - // As of React Native 0.67, instance is set AFTER CxxModule::getMethods() is invoked. - // Use getInstance() directly once - // https://github.com/facebook/react-native/commit/1d45b20b6c6ba66df0485cdb9be36463d96cf182 becomes available. - [persistor = m_blobPersistor, weakState = weak_ptr(m_sharedState)](dynamic args) { - auto parts = jsArgAsArray(args, 0); // Array + [resource = m_resource](dynamic args) { + auto dynamicParts = jsArgAsArray(args, 0); // Array + auto parts = Modules::ToJSValue(dynamicParts); auto blobId = jsArgAsString(args, 1); - vector buffer{}; - - for (const auto &part : parts) { - auto type = part[typeKey].asString(); - if (blobKey == type) { - auto blob = part[dataKey]; - winrt::array_view bufferPart; - try { - bufferPart = persistor->ResolveMessage( - blob[blobIdKey].asString(), blob[offsetKey].asInt(), blob[sizeKey].asInt()); - } catch (const std::exception &e) { - if (auto sharedState = weakState.lock()) { - Modules::SendEvent(sharedState->Module->getInstance(), "blobFailed", e.what()); - } - return; - } - - buffer.reserve(buffer.size() + bufferPart.size()); - buffer.insert(buffer.end(), bufferPart.begin(), bufferPart.end()); - } else if ("string" == type) { - auto data = part[dataKey].asString(); - - buffer.reserve(buffer.size() + data.size()); - buffer.insert(buffer.end(), data.begin(), data.end()); - } else { - if (auto state = weakState.lock()) { - auto message = "Invalid type for blob: " + type; - Modules::SendEvent(state->Module->getInstance(), "blobFailed", std::move(message)); - } - return; - } - } - - persistor->StoreMessage(std::move(buffer), std::move(blobId)); + + resource->CreateFromParts(parts.MoveArray(), std::move(blobId)); }}, {"release", - [persistor = m_blobPersistor](dynamic args) // blobId: string + [resource = m_resource](dynamic args) // blobId: string { auto blobId = jsArgAsString(args, 0); - persistor->RemoveMessage(std::move(blobId)); + resource->Release(std::move(blobId)); }}}; } @@ -212,165 +149,24 @@ vector BlobModule::getMethods() { #pragma endregion BlobModule -#pragma region MemoryBlobPersistor - -#pragma region IBlobPersistor - -winrt::array_view MemoryBlobPersistor::ResolveMessage(string &&blobId, int64_t offset, int64_t size) { - if (size < 1) - return {}; - - scoped_lock lock{m_mutex}; - - auto dataItr = m_blobs.find(std::move(blobId)); - // Not found. - if (dataItr == m_blobs.cend()) - throw std::invalid_argument("Blob object not found"); - - auto &bytes = (*dataItr).second; - auto endBound = static_cast(offset + size); - // Out of bounds. - if (endBound > bytes.size() || offset >= static_cast(bytes.size()) || offset < 0) - throw std::out_of_range("Offset or size out of range"); - - return winrt::array_view(bytes.data() + offset, bytes.data() + endBound); -} - -void MemoryBlobPersistor::RemoveMessage(string &&blobId) noexcept { - scoped_lock lock{m_mutex}; - - m_blobs.erase(std::move(blobId)); -} - -void MemoryBlobPersistor::StoreMessage(vector &&message, string &&blobId) noexcept { - scoped_lock lock{m_mutex}; - - m_blobs.insert_or_assign(std::move(blobId), std::move(message)); -} - -string MemoryBlobPersistor::StoreMessage(vector &&message) noexcept { - // substr(1, 36) strips curly braces from a GUID. - auto blobId = winrt::to_string(winrt::to_hstring(GuidHelper::CreateNewGuid())).substr(1, 36); - - scoped_lock lock{m_mutex}; - m_blobs.insert_or_assign(blobId, std::move(message)); - - return blobId; -} - -#pragma endregion IBlobPersistor - -#pragma endregion MemoryBlobPersistor - -#pragma region BlobWebSocketModuleContentHandler - -BlobWebSocketModuleContentHandler::BlobWebSocketModuleContentHandler(shared_ptr blobPersistor) noexcept - : m_blobPersistor{blobPersistor} {} - -#pragma region IWebSocketModuleContentHandler - -void BlobWebSocketModuleContentHandler::ProcessMessage(string &&message, dynamic ¶ms) /*override*/ { - params[dataKey] = std::move(message); -} - -void BlobWebSocketModuleContentHandler::ProcessMessage(vector &&message, dynamic ¶ms) /*override*/ { - auto blob = dynamic::object(); - blob(offsetKey, 0); - blob(sizeKey, message.size()); - blob(blobIdKey, m_blobPersistor->StoreMessage(std::move(message))); - - params[dataKey] = std::move(blob); - params[typeKey] = blobKey; -} -#pragma endregion IWebSocketModuleContentHandler - -void BlobWebSocketModuleContentHandler::Register(int64_t socketID) noexcept { - scoped_lock lock{m_mutex}; - m_socketIds.insert(socketID); -} - -void BlobWebSocketModuleContentHandler::Unregister(int64_t socketID) noexcept { - scoped_lock lock{m_mutex}; - - auto itr = m_socketIds.find(socketID); - if (itr != m_socketIds.end()) - m_socketIds.erase(itr); -} - -#pragma endregion BlobWebSocketModuleContentHandler - -#pragma region BlobModuleRequestBodyHandler - -BlobModuleRequestBodyHandler::BlobModuleRequestBodyHandler(shared_ptr blobPersistor) noexcept - : m_blobPersistor{blobPersistor} {} - -#pragma region IRequestBodyHandler - -bool BlobModuleRequestBodyHandler::Supports(dynamic &data) /*override*/ { - auto itr = data.find(blobKey); - - return itr != data.items().end() && !(*itr).second.empty(); -} - -dynamic BlobModuleRequestBodyHandler::ToRequestBody(dynamic &data, string &contentType) /*override*/ { - auto type = contentType; - if (!data[typeKey].isNull() && !data[typeKey].asString().empty()) { - type = data[typeKey].asString(); - } - if (type.empty()) { - type = "application/octet-stream"; - } - - auto blob = data[blobKey]; - auto blobId = blob[blobIdKey].asString(); - auto bytes = m_blobPersistor->ResolveMessage(std::move(blobId), blob[offsetKey].asInt(), blob[sizeKey].asInt()); - - auto result = dynamic::object(); - result(typeKey, type); - result(sizeKey, bytes.size()); - result("bytes", dynamic(bytes.cbegin(), bytes.cend())); - - return result; -} - -#pragma endregion IRequestBodyHandler - -#pragma endregion BlobModuleRequestBodyHandler - -#pragma region BlobModuleResponseHandler - -BlobModuleResponseHandler::BlobModuleResponseHandler(shared_ptr blobPersistor) noexcept - : m_blobPersistor{blobPersistor} {} - -#pragma region IResponseHandler - -bool BlobModuleResponseHandler::Supports(string &responseType) /*override*/ { - return blobKey == responseType; -} - -dynamic BlobModuleResponseHandler::ToResponseData(vector &&content) /*override*/ { - auto blob = dynamic::object(); - blob(offsetKey, 0); - blob(sizeKey, content.size()); - blob(blobIdKey, m_blobPersistor->StoreMessage(std::move(content))); - - return blob; -} - -#pragma endregion IResponseHandler - -#pragma endregion BlobModuleResponseHandler - /*extern*/ const char *GetBlobModuleName() noexcept { - return moduleName; + return s_moduleName; } /*extern*/ std::unique_ptr CreateBlobModule( IInspectable const &inspectableProperties) noexcept { - if (auto properties = inspectableProperties.try_as()) + if (auto properties = inspectableProperties.try_as()) return std::make_unique(properties); return nullptr; } +/*extern*/ const wchar_t *GetBlobTurboModuleName() noexcept { + return s_moduleNameW; +} + +/*extern*/ const msrn::ReactModuleProvider &GetBlobModuleProvider() noexcept { + return s_moduleProvider; +} + } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/BlobModule.h b/vnext/Shared/Modules/BlobModule.h index 76c5d46e5e7..45ea388e2b5 100644 --- a/vnext/Shared/Modules/BlobModule.h +++ b/vnext/Shared/Modules/BlobModule.h @@ -3,10 +3,9 @@ #pragma once -#include -#include -#include -#include +#include + +#include // React Native #include @@ -15,91 +14,45 @@ #include // Standard Library -#include #include -#include -#include #include namespace Microsoft::React { -class MemoryBlobPersistor final : public IBlobPersistor { - std::unordered_map> m_blobs; - std::mutex m_mutex; - - public: -#pragma region IBlobPersistor - - winrt::array_view ResolveMessage(std::string &&blobId, int64_t offset, int64_t size) override; - - void RemoveMessage(std::string &&blobId) noexcept override; - - void StoreMessage(std::vector &&message, std::string &&blobId) noexcept override; - - std::string StoreMessage(std::vector &&message) noexcept override; - -#pragma endregion IBlobPersistor -}; - -class BlobWebSocketModuleContentHandler final : public IWebSocketModuleContentHandler { - std::unordered_set m_socketIds; - std::mutex m_mutex; - std::shared_ptr m_blobPersistor; - - public: - BlobWebSocketModuleContentHandler(std::shared_ptr blobPersistor) noexcept; - -#pragma region IWebSocketModuleContentHandler - - void ProcessMessage(std::string &&message, folly::dynamic ¶ms) override; - - void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) override; - -#pragma endregion IWebSocketModuleContentHandler - - void Register(int64_t socketID) noexcept; +REACT_MODULE(BlobTurboModule, L"BlobModule") +struct BlobTurboModule { + using ModuleSpec = ReactNativeSpecs::BlobModuleSpec; - void Unregister(int64_t socketID) noexcept; -}; + REACT_INIT(Initialize) + void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept; -class BlobModuleRequestBodyHandler final : public IRequestBodyHandler { - std::shared_ptr m_blobPersistor; + REACT_GET_CONSTANTS(GetConstants) + ReactNativeSpecs::BlobModuleSpec_Constants GetConstants() noexcept; - public: - BlobModuleRequestBodyHandler(std::shared_ptr blobPersistor) noexcept; + REACT_METHOD(AddNetworkingHandler, L"addNetworkingHandler") + void AddNetworkingHandler() noexcept; -#pragma region IRequestBodyHandler + REACT_METHOD(AddWebSocketHandler, L"addWebSocketHandler") + void AddWebSocketHandler(double id) noexcept; - bool Supports(folly::dynamic &data) override; + REACT_METHOD(RemoveWebSocketHandler, L"removeWebSocketHandler") + void RemoveWebSocketHandler(double id) noexcept; - folly::dynamic ToRequestBody(folly::dynamic &data, std::string &contentType) override; + REACT_METHOD(SendOverSocket, L"sendOverSocket") + void SendOverSocket(winrt::Microsoft::ReactNative::JSValue &&blob, double socketID) noexcept; -#pragma endregion IRequestBodyHandler -}; + REACT_METHOD(CreateFromParts, L"createFromParts") + void CreateFromParts(std::vector &&parts, std::string &&withId) noexcept; -class BlobModuleResponseHandler final : public IResponseHandler { - std::shared_ptr m_blobPersistor; - - public: - BlobModuleResponseHandler(std::shared_ptr blobPersistor) noexcept; + REACT_METHOD(Release, L"release") + void Release(std::string &&blobId) noexcept; -#pragma region IResponseHandler - - bool Supports(std::string &responseType) override; - - folly::dynamic ToResponseData(std::vector &&content) override; - -#pragma endregion IResponseHandler + private: + std::shared_ptr m_resource; }; class BlobModule : public facebook::xplat::module::CxxModule { - std::shared_ptr m_blobPersistor; - std::shared_ptr m_contentHandler; - std::shared_ptr m_requestBodyHandler; - std::shared_ptr m_responseHandler; - - // Property bag high level reference. - winrt::Windows::Foundation::IInspectable m_inspectableProperties; + std::shared_ptr m_resource; public: enum class MethodId { @@ -114,15 +67,6 @@ class BlobModule : public facebook::xplat::module::CxxModule { BlobModule(winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept; - ~BlobModule() noexcept override; - - struct SharedState { - /// - /// Keeps a raw reference to the module object to lazily retrieve the React Instance as needed. - /// - CxxModule *Module{nullptr}; - }; - #pragma region CxxModule /// @@ -142,12 +86,6 @@ class BlobModule : public facebook::xplat::module::CxxModule { std::vector getMethods() override; #pragma endregion CxxModule - - private: - /// - /// Keeps members that can be accessed threads other than this module's owner accessible. - /// - std::shared_ptr m_sharedState; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/CxxModuleUtilities.cpp b/vnext/Shared/Modules/CxxModuleUtilities.cpp index 45c48dc7405..6eb64e8b4b5 100644 --- a/vnext/Shared/Modules/CxxModuleUtilities.cpp +++ b/vnext/Shared/Modules/CxxModuleUtilities.cpp @@ -2,6 +2,10 @@ // Licensed under the MIT License. #include "CxxModuleUtilities.h" +#include +#include + +namespace msrn = winrt::Microsoft::ReactNative; using facebook::react::Instance; using folly::dynamic; @@ -16,4 +20,32 @@ void SendEvent(weak_ptr weakReactInstance, string &&eventName, dynamic } } +void SendEvent( + msrn::ReactContext const &reactContext, + std::wstring_view &&eventName, + msrn::JSValueObject &&args) noexcept { + reactContext.EmitJSEvent(L"RCTDeviceEventEmitter", std::move(eventName), std::move(args)); +} + +void SendEvent( + msrn::ReactContext const &reactContext, + std::wstring_view &&eventName, + msrn::JSValueArray &&args) noexcept { + reactContext.EmitJSEvent(L"RCTDeviceEventEmitter", std::move(eventName), std::move(args)); +} + +msrn::JSValue ToJSValue(dynamic &value) noexcept { + auto reader = winrt::make(value); + auto result = msrn::JSValue::ReadFrom(reader); + + return result; +} + +dynamic ToDynamic(const msrn::JSValue &value) noexcept { + auto argWriter = msrn::MakeJSValueArgWriter(value); + auto result = msrn::DynamicWriter::ToDynamic(argWriter)[0]; + + return result; +} + } // namespace Microsoft::React::Modules diff --git a/vnext/Shared/Modules/CxxModuleUtilities.h b/vnext/Shared/Modules/CxxModuleUtilities.h index 00c869a953e..019f0969fc6 100644 --- a/vnext/Shared/Modules/CxxModuleUtilities.h +++ b/vnext/Shared/Modules/CxxModuleUtilities.h @@ -9,6 +9,9 @@ // React Native #include +// React Native Windows +#include + // Standard Library #include #include @@ -20,4 +23,18 @@ void SendEvent( std::string &&eventName, folly::dynamic &&args); +void SendEvent( + winrt::Microsoft::ReactNative::ReactContext const &reactContext, + std::wstring_view &&eventName, + winrt::Microsoft::ReactNative::JSValueObject &&args) noexcept; + +void SendEvent( + winrt::Microsoft::ReactNative::ReactContext const &reactContext, + std::wstring_view &&eventName, + winrt::Microsoft::ReactNative::JSValueArray &&args) noexcept; + +winrt::Microsoft::ReactNative::JSValue ToJSValue(folly::dynamic &value) noexcept; + +folly::dynamic ToDynamic(const winrt::Microsoft::ReactNative::JSValue &value) noexcept; + } // namespace Microsoft::React::Modules diff --git a/vnext/Shared/Modules/FileReaderModule.cpp b/vnext/Shared/Modules/FileReaderModule.cpp index 67cb040b52e..4be2abb0194 100644 --- a/vnext/Shared/Modules/FileReaderModule.cpp +++ b/vnext/Shared/Modules/FileReaderModule.cpp @@ -3,6 +3,7 @@ #include "FileReaderModule.h" +#include #include #include @@ -19,17 +20,18 @@ using namespace facebook::xplat; +namespace msrn = winrt::Microsoft::ReactNative; + using folly::dynamic; using std::string; using std::weak_ptr; -using winrt::Microsoft::ReactNative::IReactPropertyBag; -using winrt::Microsoft::ReactNative::ReactNonAbiValue; -using winrt::Microsoft::ReactNative::ReactPropertyBag; -using winrt::Microsoft::ReactNative::ReactPropertyId; using winrt::Windows::Foundation::IInspectable; namespace { -constexpr char moduleName[] = "FileReaderModule"; +constexpr char s_moduleName[] = "FileReaderModule"; +constexpr wchar_t s_moduleNameW[] = L"FileReaderModule"; + +msrn::ReactModuleProvider s_moduleProvider = msrn::MakeTurboModuleProvider(); } // namespace namespace Microsoft::React { @@ -37,7 +39,7 @@ namespace Microsoft::React { #pragma region FileReaderModule FileReaderModule::FileReaderModule(weak_ptr weakBlobPersistor) noexcept - : m_weakBlobPersistor{weakBlobPersistor} {} + : m_resource{IFileReaderResource::Make(weakBlobPersistor)} {} FileReaderModule::~FileReaderModule() noexcept /*override*/ {} @@ -45,7 +47,7 @@ FileReaderModule::~FileReaderModule() noexcept /*override*/ #pragma region CxxModule string FileReaderModule::getName() { - return moduleName; + return s_moduleName; } std::map FileReaderModule::getConstants() { @@ -61,41 +63,28 @@ std::vector FileReaderModule::getMethods() { /// /// "readAsDataURL", - [blobPersistor = m_weakBlobPersistor.lock()](dynamic args, Callback resolve, Callback reject) { - if (!blobPersistor) { - return reject({"Could not find Blob persistor"}); - } - + [resource = m_resource](dynamic args, Callback resolve, Callback reject) { auto blob = jsArgAsObject(args, 0); auto blobId = blob["blobId"].asString(); auto offset = blob["offset"].asInt(); auto size = blob["size"].asInt(); - winrt::array_view bytes; - try { - bytes = blobPersistor->ResolveMessage(std::move(blobId), offset, size); - } catch (const std::exception &e) { - return reject({e.what()}); - } - - auto result = string{"data:"}; auto typeItr = blob.find("type"); + string type{}; if (typeItr == blob.items().end()) { - result += "application/octet-stream"; + type = "application/octet-stream"; } else { - result += (*typeItr).second.asString(); + type = (*typeItr).second.asString(); } - result += ";base64,"; - - // https://www.boost.org/doc/libs/1_76_0/libs/serialization/doc/dataflow.html - using namespace boost::archive::iterators; - typedef base64_from_binary> encode_base64; - std::ostringstream oss; - std::copy(encode_base64(bytes.cbegin()), encode_base64(bytes.cend()), ostream_iterator(oss)); - result += oss.str(); - resolve({std::move(result)}); + resource->ReadAsDataUrl( + std::move(blobId), + offset, + size, + std::move(type), + [&resolve](string &&message) { resolve({std::move(message)}); }, + [&reject](string &&message) { reject({std::move(message)}); }); }}, {/// /// @@ -105,11 +94,7 @@ std::vector FileReaderModule::getMethods() { /// /// "readAsText", - [blobPersistor = m_weakBlobPersistor.lock()](dynamic args, Callback resolve, Callback reject) { - if (!blobPersistor) { - return reject({"Could not find Blob persistor"}); - } - + [resource = m_resource](dynamic args, Callback resolve, Callback reject) { auto blob = jsArgAsObject(args, 0); auto encoding = jsArgAsString(args, 1); // Default: "UTF-8" @@ -117,18 +102,13 @@ std::vector FileReaderModule::getMethods() { auto offset = blob["offset"].asInt(); auto size = blob["size"].asInt(); - winrt::array_view bytes; - try { - bytes = blobPersistor->ResolveMessage(std::move(blobId), offset, size); - } catch (const std::exception &e) { - return reject({e.what()}); - } - - // #9982 - Handle non-UTF8 encodings - // See https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html - auto result = string{bytes.cbegin(), bytes.cend()}; - - resolve({std::move(result)}); + resource->ReadAsText( + std::move(blobId), + offset, + size, + std::move(encoding), + [&resolve](string &&message) { resolve({std::move(message)}); }, + [&reject](string &&message) { reject({std::move(message)}); }); }}}; } @@ -136,14 +116,93 @@ std::vector FileReaderModule::getMethods() { #pragma endregion FileReaderModule +#pragma region FileReaderTurboModule + +void FileReaderTurboModule::Initialize(msrn::ReactContext const &reactContext) noexcept { + auto propId = msrn::ReactPropertyId>>{L"Blob.Persistor"}; + auto props = reactContext.Properties(); + auto prop = props.Get(propId); + m_resource = IFileReaderResource::Make(prop.Value()); +} + +/// +/// +/// Blob object with the following fields: +/// - blobId +/// - offset +/// - size +/// - type (optional) +/// +/// +/// Either resolves or rejects the current method with a given text message. +/// +/// +void FileReaderTurboModule::ReadAsDataUrl(msrn::JSValue &&data, msrn::ReactPromise &&result) noexcept { + auto &blob = data.AsObject(); + auto blobId = blob["blobId"].AsString(); + auto offset = blob["offset"].AsInt64(); + auto size = blob["size"].AsInt64(); + + auto typeItr = blob.find("type"); + string type{}; + if (typeItr == blob.end()) { + type = "application/octet-stream"; + } else { + type = (*typeItr).second.AsString(); + } + + m_resource->ReadAsDataUrl( + std::move(blobId), + offset, + size, + std::move(type), + [&result](string &&message) { result.Resolve(std::move(message)); }, + [&result](string &&message) { result.Reject(winrt::to_hstring(std::move(message)).c_str()); }); +} + +/// +/// +/// Blob object with the following fields: +/// - blobId +/// - offset +/// - size +/// - type (optional) +/// +/// +/// Text encoding to proces data with. +/// +/// +/// Either resolves or rejects the current method with a given text message. +/// +/// +void FileReaderTurboModule::ReadAsText( + msrn::JSValue &&data, + string &&encoding, + msrn::ReactPromise &&result) noexcept { + auto &blob = data.AsObject(); + auto blobId = blob["blobId"].AsString(); + auto offset = blob["offset"].AsInt64(); + auto size = blob["size"].AsInt64(); + + m_resource->ReadAsText( + std::move(blobId), + offset, + size, + std::move(encoding), + [&result](string &&message) { result.Resolve(std::move(message)); }, + [&result](string &&message) { result.Reject(winrt::to_hstring(std::move(message)).c_str()); }); +} + +#pragma endregion FileReaderTurboModule + /*extern*/ const char *GetFileReaderModuleName() noexcept { - return moduleName; + return s_moduleName; } /*extern*/ std::unique_ptr CreateFileReaderModule( IInspectable const &inspectableProperties) noexcept { - auto propId = ReactPropertyId>>{L"Blob.Persistor"}; - auto propBag = ReactPropertyBag{inspectableProperties.try_as()}; + auto propId = msrn::ReactPropertyId>>{L"Blob.Persistor"}; + auto propBag = msrn::ReactPropertyBag{inspectableProperties.try_as()}; if (auto prop = propBag.Get(propId)) { auto weakBlobPersistor = prop.Value(); @@ -154,4 +213,12 @@ std::vector FileReaderModule::getMethods() { return nullptr; } +/*extern*/ const wchar_t *GetFileReaderTurboModuleName() noexcept { + return s_moduleNameW; +} + +/*extern*/ const msrn::ReactModuleProvider &GetFileReaderModuleProvider() noexcept { + return s_moduleProvider; +} + } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/FileReaderModule.h b/vnext/Shared/Modules/FileReaderModule.h index bde28680a45..40aef59e3e1 100644 --- a/vnext/Shared/Modules/FileReaderModule.h +++ b/vnext/Shared/Modules/FileReaderModule.h @@ -3,6 +3,10 @@ #pragma once +#include + +#include +#include #include "IBlobPersistor.h" // React Native @@ -48,7 +52,29 @@ class FileReaderModule : public facebook::xplat::module::CxxModule { #pragma endregion CxxModule private: - std::weak_ptr m_weakBlobPersistor; + std::shared_ptr m_resource; +}; + +REACT_MODULE(FileReaderTurboModule, L"FileReaderModule") +struct FileReaderTurboModule { + using ModuleSpec = ReactNativeSpecs::FileReaderModuleSpec; + + REACT_INIT(Initialize) + void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept; + + REACT_METHOD(ReadAsDataUrl, L"readAsDataURL") + void ReadAsDataUrl( + winrt::Microsoft::ReactNative::JSValue &&data, + winrt::Microsoft::ReactNative::ReactPromise &&result) noexcept; + + REACT_METHOD(ReadAsText, L"readAsText") + void ReadAsText( + winrt::Microsoft::ReactNative::JSValue &&data, + std::string &&encoding, + winrt::Microsoft::ReactNative::ReactPromise &&result) noexcept; + + private: + std::shared_ptr m_resource; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 7995320d961..f3941b31e40 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -5,6 +5,7 @@ #include "HttpModule.h" +#include #include #include @@ -14,6 +15,7 @@ using facebook::react::Instance; using folly::dynamic; +using std::function; using std::shared_ptr; using std::string; using std::weak_ptr; @@ -23,12 +25,15 @@ using winrt::Microsoft::ReactNative::ReactPropertyBag; using winrt::Microsoft::ReactNative::ReactPropertyId; using winrt::Windows::Foundation::IInspectable; +namespace msrn = winrt::Microsoft::ReactNative; + namespace { using Microsoft::React::Modules::SendEvent; using Microsoft::React::Networking::IHttpResource; -constexpr char moduleName[] = "Networking"; +constexpr char s_moduleName[] = "Networking"; +constexpr wchar_t s_moduleNameW[] = L"Networking"; // React event names constexpr char completedResponse[] = "didCompleteNetworkResponse"; @@ -38,6 +43,15 @@ constexpr char receivedIncrementalData[] = "didReceiveNetworkIncrementalData"; constexpr char receivedDataProgress[] = "didReceiveNetworkDataProgress"; constexpr char receivedData[] = "didReceiveNetworkData"; +constexpr wchar_t completedResponseW[] = L"didCompleteNetworkResponse"; +constexpr wchar_t receivedResponseW[] = L"didReceiveNetworkResponse"; +constexpr wchar_t sentDataW[] = L"didSendNetworkData"; +constexpr wchar_t receivedIncrementalDataW[] = L"didReceiveNetworkIncrementalData"; +constexpr wchar_t receivedDataProgressW[] = L"didReceiveNetworkDataProgress"; +constexpr wchar_t receivedDataW[] = L"didReceiveNetworkData"; + +msrn::ReactModuleProvider s_moduleProvider = msrn::MakeTurboModuleProvider(); + static void SetUpHttpResource( shared_ptr resource, weak_ptr weakReactInstance, @@ -65,10 +79,11 @@ static void SetUpHttpResource( }); // Explicitly declaring function type to avoid type inference ambiguity. - std::function onDataDynamic = [weakReactInstance]( - int64_t requestId, dynamic &&responseData) { - SendEvent(weakReactInstance, receivedData, dynamic::array(requestId, std::move(responseData))); - }; + function onDataDynamic = + [weakReactInstance](int64_t requestId, msrn::JSValueObject &&responseData) { + auto responseDynamic = Microsoft::React::Modules::ToDynamic(msrn::JSValue{std::move(responseData)}); + SendEvent(weakReactInstance, receivedData, dynamic::array(requestId, std::move(responseDynamic))); + }; resource->SetOnData(std::move(onDataDynamic)); resource->SetOnIncrementalData( @@ -101,6 +116,104 @@ static void SetUpHttpResource( namespace Microsoft::React { +#pragma region HttpTurboModule + +void HttpTurboModule::Initialize(msrn::ReactContext const &reactContext) noexcept { + m_context = reactContext; + m_resource = IHttpResource::Make(m_context.Properties().Handle()); + + m_resource->SetOnRequestSuccess([context = m_context](int64_t requestId) { + SendEvent(context, completedResponseW, msrn::JSValueArray{requestId}); + }); + + m_resource->SetOnResponse([context = m_context](int64_t requestId, IHttpResource::Response &&response) { + auto headers = msrn::JSValueObject{}; + for (auto &header : response.Headers) { + headers[header.first] = header.second; + } + + // TODO: Test response content? + auto args = msrn::JSValueArray{requestId, response.StatusCode, std::move(headers), response.Url}; + + SendEvent(context, receivedResponseW, std::move(args)); + }); + + m_resource->SetOnData([context = m_context](int64_t requestId, string &&responseData) { + SendEvent(context, receivedDataW, msrn::JSValueArray{requestId, std::move(responseData)}); + }); + + // Explicitly declaring function type to avoid type inference ambiguity. + function onDataObject = + [context = m_context](int64_t requestId, msrn::JSValueObject &&responseData) { + SendEvent(context, receivedDataW, msrn::JSValueArray{requestId, std::move(responseData)}); + }; + m_resource->SetOnData(std::move(onDataObject)); + + m_resource->SetOnIncrementalData( + [context = m_context](int64_t requestId, string &&responseData, int64_t progress, int64_t total) { + SendEvent( + context, receivedIncrementalDataW, msrn::JSValueArray{requestId, std::move(responseData), progress, total}); + }); + + m_resource->SetOnDataProgress([context = m_context](int64_t requestId, int64_t progress, int64_t total) { + SendEvent(context, receivedDataProgressW, msrn::JSValueArray{requestId, progress, total}); + }); + + m_resource->SetOnResponseComplete([context = m_context](int64_t requestId) { + SendEvent(context, completedResponseW, msrn::JSValueArray{requestId}); + }); + + m_resource->SetOnError([context = m_context](int64_t requestId, string &&message, bool isTimeout) { + auto args = msrn::JSValueArray{requestId, std::move(message)}; + if (isTimeout) { + args.push_back(true); + } + + SendEvent(context, completedResponseW, std::move(args)); + }); +} + +void HttpTurboModule::SendRequest( + ReactNativeSpecs::NetworkingIOSSpec_sendRequest_query &&query, + function const &callback) noexcept { + m_requestId++; + auto &headersObj = query.headers.AsObject(); + IHttpResource::Headers headers; + for (auto &entry : headersObj) { + headers.emplace(entry.first, entry.second.AsString()); + } + + m_resource->SendRequest( + std::move(query.method), + std::move(query.url), + m_requestId, + std::move(headers), + query.data.MoveObject(), + std::move(query.responseType), + query.incrementalUpdates, + static_cast(query.timeout), + query.withCredentials, + [&callback](int64_t requestId) { callback({static_cast(requestId)}); }); +} + +void HttpTurboModule::AbortRequest(double requestId) noexcept { + m_resource->AbortRequest(static_cast(requestId)); +} + +void HttpTurboModule::ClearCookies(function const &callback) noexcept { + m_resource->ClearCookies(); +} + +void HttpTurboModule::AddListener(string &&eventName) noexcept { /*NOOP*/ +} + +void HttpTurboModule::RemoveListeners(double count) noexcept { /*NOOP*/ +} + +#pragma endregion HttpTurboModule + +#pragma region HttpModule + HttpModule::HttpModule(IInspectable const &inspectableProperties) noexcept : m_holder{std::make_shared()}, m_inspectableProperties{inspectableProperties}, @@ -115,7 +228,7 @@ HttpModule::~HttpModule() noexcept /*override*/ { #pragma region CxxModule string HttpModule::getName() /*override*/ { - return moduleName; + return s_moduleName; } std::map HttpModule::getConstants() { @@ -142,6 +255,7 @@ std::vector HttpModule::getMethods() SetUpHttpResource(resource, holder->Module->getInstance(), holder->Module->m_inspectableProperties); holder->Module->m_isResourceSetup = true; } + holder->Module->m_requestId++; auto params = facebook::xplat::jsArgAsObject(args, 0); IHttpResource::Headers headers; @@ -152,9 +266,9 @@ std::vector HttpModule::getMethods() resource->SendRequest( params["method"].asString(), params["url"].asString(), - params["requestId"].asInt(), + holder->Module->m_requestId, std::move(headers), - std::move(params["data"]), + Modules::ToJSValue(params["data"]).MoveObject(), params["responseType"].asString(), params["incrementalUpdates"].asBool(), static_cast(params["timeout"].asDouble()), @@ -211,8 +325,18 @@ std::vector HttpModule::getMethods() #pragma endregion CxxModule +#pragma endregion HttpModule + /*extern*/ const char *GetHttpModuleName() noexcept { - return moduleName; + return s_moduleName; +} + +/*extern*/ const wchar_t *GetHttpTurboModuleName() noexcept { + return s_moduleNameW; +} + +/*extern*/ const msrn::ReactModuleProvider &GetHttpModuleProvider() noexcept { + return s_moduleProvider; } } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/HttpModule.h b/vnext/Shared/Modules/HttpModule.h index 4dbd28895a3..94c5ca2032b 100644 --- a/vnext/Shared/Modules/HttpModule.h +++ b/vnext/Shared/Modules/HttpModule.h @@ -3,6 +3,8 @@ #pragma once +#include +#include #include // React Native @@ -13,6 +15,36 @@ namespace Microsoft::React { +REACT_MODULE(HttpTurboModule, L"Networking") +struct HttpTurboModule { + using ModuleSpec = ReactNativeSpecs::NetworkingIOSSpec; + + REACT_INIT(Initialize) + void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept; + + REACT_METHOD(SendRequest, L"sendRequest") + void SendRequest( + ReactNativeSpecs::NetworkingIOSSpec_sendRequest_query &&query, + std::function const &callback) noexcept; + + REACT_METHOD(AbortRequest, L"abortRequest") + void AbortRequest(double requestId) noexcept; + + REACT_METHOD(ClearCookies, L"clearCookies") + void ClearCookies(std::function const &callback) noexcept; + + REACT_METHOD(AddListener, L"addListener") + void AddListener(std::string &&eventName) noexcept; + + REACT_METHOD(RemoveListeners, L"removeListeners") + void RemoveListeners(double count) noexcept; + + private: + std::shared_ptr m_resource; + winrt::Microsoft::ReactNative::ReactContext m_context; + int64_t m_requestId{0}; +}; + /// /// Realizes NativeModules projection. /// See src\Libraries\Network\RCTNetworking.windows.js @@ -53,6 +85,7 @@ class HttpModule : public facebook::xplat::module::CxxModule { std::shared_ptr m_resource; std::shared_ptr m_holder; bool m_isResourceSetup{false}; + int64_t m_requestId{0}; // Property bag high level reference. winrt::Windows::Foundation::IInspectable m_inspectableProperties; diff --git a/vnext/Shared/Modules/IRequestBodyHandler.h b/vnext/Shared/Modules/IRequestBodyHandler.h index 34fe0d3405c..a1a287d9e75 100644 --- a/vnext/Shared/Modules/IRequestBodyHandler.h +++ b/vnext/Shared/Modules/IRequestBodyHandler.h @@ -3,8 +3,8 @@ #pragma once -// Folly -#include +// React Native Windows +#include // Standard Library #include @@ -26,7 +26,7 @@ struct IRequestBodyHandler { /// true - contains a blob reference. /// false - does not contain a blob reference. /// - virtual bool Supports(folly::dynamic &data) = 0; + virtual bool Supports(winrt::Microsoft::ReactNative::JSValueObject &data) = 0; /// /// Returns the {@link RequestBody} for the JS body payload. @@ -46,7 +46,9 @@ struct IRequestBodyHandler { /// "bytes" - Raw body content /// NOTE: This is an arbitrary key. Pending non-folly structured object to model request body. /// - virtual folly::dynamic ToRequestBody(folly::dynamic &data, std::string &contentType) = 0; + virtual winrt::Microsoft::ReactNative::JSValueObject ToRequestBody( + winrt::Microsoft::ReactNative::JSValueObject &data, + std::string &contentType) = 0; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/IResponseHandler.h b/vnext/Shared/Modules/IResponseHandler.h index abbc78f7e60..d8e28b52b4f 100644 --- a/vnext/Shared/Modules/IResponseHandler.h +++ b/vnext/Shared/Modules/IResponseHandler.h @@ -1,7 +1,7 @@ #pragma once -// Folly -#include +// React Native Windows +#include // Standard Library #include @@ -21,7 +21,7 @@ struct IResponseHandler { /// /// Returns the JS body payload for the {@link ResponseBody}. /// - virtual folly::dynamic ToResponseData(std::vector &&content) = 0; + virtual winrt::Microsoft::ReactNative::JSValueObject ToResponseData(std::vector &&content) = 0; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/IUriHandler.h b/vnext/Shared/Modules/IUriHandler.h index fa96c251cef..0720b684066 100644 --- a/vnext/Shared/Modules/IUriHandler.h +++ b/vnext/Shared/Modules/IUriHandler.h @@ -3,8 +3,8 @@ #pragma once -// Folly -#include +// React Native Windows +#include // Standard Library #include @@ -31,7 +31,7 @@ struct IUriHandler { /// "size" - Number of bytes fetched from blob /// "name" - File name obtained from the URI /// "lastModified - Last write to local file in milliseconds - virtual folly::dynamic Fetch(std::string &uri) = 0; + virtual winrt::Microsoft::ReactNative::JSValueObject Fetch(std::string &uri) = 0; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h index 97c06f158f9..4d508603865 100644 --- a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h +++ b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h @@ -3,8 +3,8 @@ #pragma once -// React Native -#include +// React Native Windows +#include // Standard Library #include @@ -18,9 +18,11 @@ namespace Microsoft::React { struct IWebSocketModuleContentHandler { virtual ~IWebSocketModuleContentHandler() noexcept {} - virtual void ProcessMessage(std::string &&message, folly::dynamic ¶ms) = 0; + virtual void ProcessMessage(std::string &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; - virtual void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) = 0; + virtual void ProcessMessage( + std::vector &&message, + winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/WebSocketModule.cpp b/vnext/Shared/Modules/WebSocketModule.cpp index 2ccb180e26e..6d5aa9414bc 100644 --- a/vnext/Shared/Modules/WebSocketModule.cpp +++ b/vnext/Shared/Modules/WebSocketModule.cpp @@ -4,11 +4,16 @@ #include "pch.h" #include +#include +#include #include #include #include +// fmt +#include + // React Native #include #include @@ -19,6 +24,8 @@ // Standard Libriary #include +namespace msrn = winrt::Microsoft::ReactNative; + using namespace facebook::xplat; using facebook::react::Instance; @@ -26,6 +33,7 @@ using folly::dynamic; using std::shared_ptr; using std::string; +using std::vector; using std::weak_ptr; using winrt::Microsoft::ReactNative::IReactPropertyBag; @@ -42,7 +50,10 @@ using Microsoft::React::WebSocketModule; using Microsoft::React::Modules::SendEvent; using Microsoft::React::Networking::IWebSocketResource; -constexpr char moduleName[] = "WebSocketModule"; +constexpr char s_moduleName[] = "WebSocketModule"; +constexpr wchar_t s_moduleNameW[] = L"WebSocketModule"; + +msrn::ReactModuleProvider s_moduleProvider = msrn::MakeTurboModuleProvider(); static shared_ptr GetOrCreateWebSocket(int64_t id, string &&url, weak_ptr weakState) { @@ -105,7 +116,7 @@ GetOrCreateWebSocket(int64_t id, string &&url, weak_ptr contentHandler; auto propId = ReactPropertyId>>{ L"BlobModule.ContentHandler"}; @@ -117,7 +128,7 @@ GetOrCreateWebSocket(int64_t id, string &&url, weak_ptr arr; CryptographicBuffer::CopyToByteArray(buffer, arr); - auto data = std::vector(arr.begin(), arr.end()); + auto data = vector(arr.begin(), arr.end()); contentHandler->ProcessMessage(std::move(data), args); } else { @@ -127,7 +138,7 @@ GetOrCreateWebSocket(int64_t id, string &&url, weak_ptrSetOnClose([id, weakInstance](IWebSocketResource::CloseCode code, const string &reason) { auto strongInstance = weakInstance.lock(); @@ -179,7 +190,7 @@ void WebSocketModule::SetResourceFactory( } string WebSocketModule::getName() { - return moduleName; + return s_moduleName; } std::map WebSocketModule::getConstants() { @@ -187,7 +198,7 @@ std::map WebSocketModule::getConstants() { } // clang-format off -std::vector WebSocketModule::getMethods() +vector WebSocketModule::getMethods() { return { @@ -312,8 +323,172 @@ void WebSocketModuleProxy::SendBinary(std::string &&base64String, int64_t id) no #pragma endregion WebSocketModuleProxy +#pragma region WebSocketTurboModule + +shared_ptr WebSocketTurboModule::CreateResource(int64_t id, string &&url) noexcept { + shared_ptr rc; + try { + rc = IWebSocketResource::Make(); + } catch (const winrt::hresult_error &e) { + auto error = fmt::format("[0x{:0>8x}] {}", static_cast(e.code()), winrt::to_string(e.message())); + SendEvent(m_context, L"webSocketFailed", {{"id", id}, {"message", std::move(error)}}); + + return nullptr; + } catch (const std::exception &e) { + SendEvent(m_context, L"webSocketFailed", {{"id", id}, {"message", e.what()}}); + + return nullptr; + } catch (...) { + SendEvent( + m_context, L"webSocketFailed", {{"id", id}, {"message", "Unidentified error creating IWebSocketResource"}}); + + return nullptr; + } + + // Set up resource + rc->SetOnConnect([id, context = m_context]() { + SendEvent(context, L"websocketOpen", msrn::JSValueObject{{"id", id}}); + }); + + rc->SetOnMessage([id, context = m_context](size_t length, const string &message, bool isBinary) { + auto args = msrn::JSValueObject{{"id", id}, {"type", isBinary ? "binary" : "text"}}; + shared_ptr contentHandler; + auto propId = + ReactPropertyId>>{L"BlobModule.ContentHandler"}; + auto propBag = context.Properties(); + if (auto prop = propBag.Get(propId)) + contentHandler = prop.Value().lock(); + + if (contentHandler) { + if (isBinary) { + auto buffer = CryptographicBuffer::DecodeFromBase64String(winrt::to_hstring(message)); + winrt::com_array arr; + CryptographicBuffer::CopyToByteArray(buffer, arr); + auto data = vector(arr.begin(), arr.end()); + + contentHandler->ProcessMessage(std::move(data), args); + } else { + contentHandler->ProcessMessage(string{message}, args); + } + } else { + args["data"] = message; + } + + SendEvent(context, L"websocketMessage", std::move(args)); + }); + + rc->SetOnClose([id, context = m_context](IWebSocketResource::CloseCode code, const string &reason) { + auto args = msrn::JSValueObject{{"id", id}, {"code", static_cast(code)}, {"reason", reason}}; + + SendEvent(context, L"websocketClosed", std::move(args)); + }); + + rc->SetOnError([id, context = m_context](const IWebSocketResource::Error &err) { + auto errorObj = msrn::JSValueObject{{"id", id}, {"message", err.Message}}; + + SendEvent(context, L"websocketFailed", std::move(errorObj)); + }); + + m_resourceMap.emplace(static_cast(id), rc); + + return rc; +} + +void WebSocketTurboModule::Initialize(msrn::ReactContext const &reactContext) noexcept { + m_context = reactContext.Handle(); +} + +void WebSocketTurboModule::Connect( + string &&url, + std::optional> protocols, + ReactNativeSpecs::WebSocketModuleSpec_connect_options &&options, + double socketID) noexcept { + IWebSocketResource::Protocols rcProtocols; + for (const auto &protocol : protocols.value_or(vector{})) { + rcProtocols.push_back(protocol); + } + + IWebSocketResource::Options rcOptions; + auto &optHeaders = options.headers; + if (optHeaders.has_value()) { + auto &headersVal = optHeaders.value(); + for (const auto &headerVal : headersVal.AsArray()) { + // Each header JSValueObject should only contain one key-value pair + const auto &entry = *headerVal.AsObject().cbegin(); + rcOptions.emplace(winrt::to_hstring(entry.first), entry.second.AsString()); + } + } + + weak_ptr weakRc; + auto rcItr = m_resourceMap.find(socketID); + if (rcItr != m_resourceMap.cend()) { + weakRc = (*rcItr).second; + } else { + weakRc = CreateResource(static_cast(socketID), string{url}); + } + + if (auto rc = weakRc.lock()) { + rc->Connect(std::move(url), rcProtocols, rcOptions); + } +} + +void WebSocketTurboModule::Close(double code, string &&reason, double socketID) noexcept { + auto rcItr = m_resourceMap.find(socketID); + if (rcItr == m_resourceMap.cend()) { + return; // TODO: Send error instead? + } + + weak_ptr weakRc = (*rcItr).second; + if (auto rc = weakRc.lock()) { + rc->Close(static_cast(code), std::move(reason)); + } +} + +void WebSocketTurboModule::Send(string &&message, double forSocketID) noexcept { + auto rcItr = m_resourceMap.find(forSocketID); + if (rcItr == m_resourceMap.cend()) { + return; // TODO: Send error instead? + } + + weak_ptr weakRc = (*rcItr).second; + if (auto rc = weakRc.lock()) { + rc->Send(std::move(message)); + } +} + +void WebSocketTurboModule::SendBinary(string &&base64String, double forSocketID) noexcept { + auto rcItr = m_resourceMap.find(forSocketID); + if (rcItr == m_resourceMap.cend()) { + return; // TODO: Send error instead? + } + + weak_ptr weakRc = (*rcItr).second; + if (auto rc = weakRc.lock()) { + rc->SendBinary(std::move(base64String)); + } +} + +void WebSocketTurboModule::Ping(double socketID) noexcept { + auto rcItr = m_resourceMap.find(socketID); + if (rcItr == m_resourceMap.cend()) { + return; // TODO: Send error instead? + } + + weak_ptr weakRc = (*rcItr).second; + if (auto rc = weakRc.lock()) { + rc->Ping(); + } +} + +// See react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java +void WebSocketTurboModule::AddListener(string && /*eventName*/) noexcept {} + +void WebSocketTurboModule::RemoveListeners(double /*count*/) noexcept {} + +#pragma endregion WebSocketTurboModule + /*extern*/ const char *GetWebSocketModuleName() noexcept { - return moduleName; + return s_moduleName; } /*extern*/ std::unique_ptr CreateWebSocketModule( @@ -324,4 +499,12 @@ void WebSocketModuleProxy::SendBinary(std::string &&base64String, int64_t id) no return nullptr; } +/*extern*/ const wchar_t *GetWebSocketTurboModuleName() noexcept { + return s_moduleNameW; +} + +/*extern*/ const msrn::ReactModuleProvider &GetWebSocketModuleProvider() noexcept { + return s_moduleProvider; +} + } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/WebSocketTurboModule.h b/vnext/Shared/Modules/WebSocketTurboModule.h new file mode 100644 index 00000000000..030df965c93 --- /dev/null +++ b/vnext/Shared/Modules/WebSocketTurboModule.h @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include +#include + +namespace Microsoft::React { + +REACT_MODULE(WebSocketTurboModule, L"WebSocketModule") +struct WebSocketTurboModule { + using ModuleSpec = ReactNativeSpecs::WebSocketModuleSpec; + + REACT_INIT(Initialize) + void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept; + + REACT_METHOD(Connect, L"connect") + void Connect( + std::string &&url, + std::optional> protocols, + ReactNativeSpecs::WebSocketModuleSpec_connect_options &&options, + double socketID) noexcept; + + REACT_METHOD(Close, L"close") + void Close(double code, std::string &&reason, double socketID) noexcept; + + REACT_METHOD(Send, L"send") + void Send(std::string &&message, double forSocketID) noexcept; + + REACT_METHOD(SendBinary, L"sendBinary") + void SendBinary(std::string &&base64String, double forSocketID) noexcept; + + REACT_METHOD(Ping, L"ping") + void Ping(double socketID) noexcept; + + REACT_METHOD(AddListener, L"addListener") + void AddListener(std::string &&eventName) noexcept; + + REACT_METHOD(RemoveListeners, L"removeListeners") + void RemoveListeners(double count) noexcept; + + private: + std::shared_ptr CreateResource(int64_t id, std::string &&url) noexcept; + + winrt::Microsoft::ReactNative::ReactContext m_context; + std::unordered_map> m_resourceMap; +}; + +} // namespace Microsoft::React diff --git a/vnext/Shared/Networking/DefaultBlobResource.cpp b/vnext/Shared/Networking/DefaultBlobResource.cpp new file mode 100644 index 00000000000..5f08a0240fc --- /dev/null +++ b/vnext/Shared/Networking/DefaultBlobResource.cpp @@ -0,0 +1,323 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "DefaultBlobResource.h" + +#include +#include + +// Boost Libraries +#include + +// Windows API +#include + +using std::scoped_lock; +using std::shared_ptr; +using std::string; +using std::vector; +using std::weak_ptr; +using winrt::array_view; +using winrt::Windows::Security::Cryptography::CryptographicBuffer; + +namespace msrn = winrt::Microsoft::ReactNative; + +namespace { + +constexpr Microsoft::React::Networking::IBlobResource::BlobFieldNames + blobKeys{"blob", "blobId", "offset", "size", "type", "data"}; + +} // namespace + +namespace Microsoft::React::Networking { + +#pragma region DefaultBlobResource + +DefaultBlobResource::DefaultBlobResource( + shared_ptr blobPersistor, + shared_ptr contentHandler, + shared_ptr requestBodyHandler, + shared_ptr responseHandler, + msrn::ReactPropertyBag propertyBag) + : m_blobPersistor{blobPersistor}, + m_contentHandler{contentHandler}, + m_requestBodyHandler{requestBodyHandler}, + m_responseHandler{responseHandler}, + m_propertyBag{propertyBag} {} + +#pragma region IBlobResource + +/*static*/ const IBlobResource::BlobFieldNames &IBlobResource::FieldNames() noexcept { + return blobKeys; +} + +/*static*/ shared_ptr IBlobResource::Make( + winrt::Windows::Foundation::IInspectable const &inspectableProperties) { + using namespace msrn; + + auto propBag = ReactPropertyBag{inspectableProperties.try_as()}; + + auto blobPersistor = std::make_shared(); + auto contentHandler = std::make_shared(blobPersistor); + auto requestBodyHanlder = std::make_shared(blobPersistor); + auto responseHandler = std::make_shared(blobPersistor); + + auto contentHandlerPropId = + ReactPropertyId>>{L"BlobModule.ContentHandler"}; + propBag.Set(contentHandlerPropId, weak_ptr{contentHandler}); + + auto blobPersistorPropId = ReactPropertyId>>{L"Blob.Persistor"}; + ; + propBag.Set(blobPersistorPropId, weak_ptr{blobPersistor}); + + auto result = std::make_shared( + blobPersistor, contentHandler, requestBodyHanlder, responseHandler, propBag); + + return result; +} + +void DefaultBlobResource::SendOverSocket(string &&blobId, int64_t offset, int64_t size, int64_t socketId) noexcept +/*override*/ { + auto propId = + msrn::ReactPropertyId>>{L"WebSocketModule.Proxy"}; + shared_ptr wsProxy; + if (auto prop = m_propertyBag.Get(propId)) { + wsProxy = prop.Value().lock(); + } + if (!wsProxy) { + return; + } + + array_view data; + try { + data = m_blobPersistor->ResolveMessage(std::move(blobId), offset, size); + } catch (const std::exception &e) { + return m_callbacks.OnError(e.what()); + } + + auto buffer = CryptographicBuffer::CreateFromByteArray(data); + auto base64Hstring = CryptographicBuffer::EncodeToBase64String(std::move(buffer)); + auto base64String = winrt::to_string(base64Hstring); + + wsProxy->SendBinary(std::move(base64String), socketId); +} + +void DefaultBlobResource::CreateFromParts(msrn::JSValueArray &&parts, string &&blobId) noexcept /*override*/ { + vector buffer{}; + + for (const auto &partItem : parts) { + auto &part = partItem.AsObject(); + auto type = part.at(blobKeys.Type).AsString(); + if (blobKeys.Blob == type) { + auto &blob = part.at(blobKeys.Data).AsObject(); + array_view bufferPart; + try { + bufferPart = m_blobPersistor->ResolveMessage( + blob.at(blobKeys.BlobId).AsString(), blob.at(blobKeys.Offset).AsInt64(), blob.at(blobKeys.Size).AsInt64()); + } catch (const std::exception &e) { + return m_callbacks.OnError(e.what()); + } + + buffer.reserve(buffer.size() + bufferPart.size()); + buffer.insert(buffer.end(), bufferPart.begin(), bufferPart.end()); + } else if ("string" == type) { + auto data = part.at(blobKeys.Data).AsString(); + + buffer.reserve(buffer.size() + data.size()); + buffer.insert(buffer.end(), data.begin(), data.end()); + } else { + return m_callbacks.OnError("Invalid type for blob: " + type); + } + } + + m_blobPersistor->StoreMessage(std::move(buffer), std::move(blobId)); +} + +void DefaultBlobResource::Release(string &&blobId) noexcept /*override*/ { + m_blobPersistor->RemoveMessage(std::move(blobId)); +} + +void DefaultBlobResource::AddNetworkingHandler() noexcept /*override*/ { + auto propId = msrn::ReactPropertyId>>{L"HttpModule.Proxy"}; + + if (auto prop = m_propertyBag.Get(propId)) { + if (auto httpHandler = prop.Value().lock()) { + httpHandler->AddRequestBodyHandler(m_requestBodyHandler); + httpHandler->AddResponseHandler(m_responseHandler); + } + } + // TODO: else emit error? +} + +void DefaultBlobResource::AddWebSocketHandler(int64_t id) noexcept /*override*/ { + m_contentHandler->Register(id); +} + +void DefaultBlobResource::RemoveWebSocketHandler(int64_t id) noexcept /*override*/ { + m_contentHandler->Unregister(id); +} + +IBlobResource::BlobCallbacks &DefaultBlobResource::Callbacks() noexcept /*override*/ { + return m_callbacks; +} + +#pragma endregion IBlobResource + +#pragma endregion DefaultBlobResource + +#pragma region MemoryBlobPersistor + +#pragma region IBlobPersistor + +array_view MemoryBlobPersistor::ResolveMessage(string &&blobId, int64_t offset, int64_t size) { + if (size < 1) + return {}; + + scoped_lock lock{m_mutex}; + + auto dataItr = m_blobs.find(std::move(blobId)); + // Not found. + if (dataItr == m_blobs.cend()) + throw std::invalid_argument("Blob object not found"); + + auto &bytes = (*dataItr).second; + auto endBound = static_cast(offset + size); + // Out of bounds. + if (endBound > bytes.size() || offset >= static_cast(bytes.size()) || offset < 0) + throw std::out_of_range("Offset or size out of range"); + + return array_view(bytes.data() + offset, bytes.data() + endBound); +} + +void MemoryBlobPersistor::RemoveMessage(string &&blobId) noexcept { + scoped_lock lock{m_mutex}; + + m_blobs.erase(std::move(blobId)); +} + +void MemoryBlobPersistor::StoreMessage(vector &&message, string &&blobId) noexcept { + scoped_lock lock{m_mutex}; + + m_blobs.insert_or_assign(std::move(blobId), std::move(message)); +} + +string MemoryBlobPersistor::StoreMessage(vector &&message) noexcept { + auto blobId = boost::uuids::to_string(m_guidGenerator()); + + scoped_lock lock{m_mutex}; + m_blobs.insert_or_assign(blobId, std::move(message)); + + return blobId; +} + +#pragma endregion IBlobPersistor + +#pragma endregion MemoryBlobPersistor + +#pragma region BlobWebSocketModuleContentHandler + +BlobWebSocketModuleContentHandler::BlobWebSocketModuleContentHandler(shared_ptr blobPersistor) noexcept + : m_blobPersistor{blobPersistor} {} + +#pragma region IWebSocketModuleContentHandler + +void BlobWebSocketModuleContentHandler::ProcessMessage( + string &&message, + msrn::JSValueObject ¶ms) noexcept /*override*/ +{ + params[blobKeys.Data] = std::move(message); +} + +void BlobWebSocketModuleContentHandler::ProcessMessage( + vector &&message, + msrn::JSValueObject ¶ms) noexcept /*override*/ +{ + auto blob = msrn::JSValueObject{ + {blobKeys.Offset, 0}, + {blobKeys.Size, message.size()}, + {blobKeys.BlobId, m_blobPersistor->StoreMessage(std::move(message))}}; + + params[blobKeys.Data] = std::move(blob); + params[blobKeys.Type] = blobKeys.Blob; +} + +#pragma endregion IWebSocketModuleContentHandler + +void BlobWebSocketModuleContentHandler::Register(int64_t socketID) noexcept { + scoped_lock lock{m_mutex}; + m_socketIds.insert(socketID); +} + +void BlobWebSocketModuleContentHandler::Unregister(int64_t socketID) noexcept { + scoped_lock lock{m_mutex}; + + auto itr = m_socketIds.find(socketID); + if (itr != m_socketIds.end()) + m_socketIds.erase(itr); +} + +#pragma endregion BlobWebSocketModuleContentHandler + +#pragma region BlobModuleRequestBodyHandler + +BlobModuleRequestBodyHandler::BlobModuleRequestBodyHandler(shared_ptr blobPersistor) noexcept + : m_blobPersistor{blobPersistor} {} + +#pragma region IRequestBodyHandler + +bool BlobModuleRequestBodyHandler::Supports(msrn::JSValueObject &data) /*override*/ { + auto itr = data.find(blobKeys.Blob); + + return itr != data.cend() && !(*itr).second.AsString().empty(); +} + +msrn::JSValueObject BlobModuleRequestBodyHandler::ToRequestBody( + msrn::JSValueObject &data, + string &contentType) /*override*/ { + auto type = contentType; + auto itr = data.find(blobKeys.Type); + if (itr != data.cend() && !(*itr).second.AsString().empty()) { + type = (*itr).second.AsString(); + } + if (type.empty()) { + type = "application/octet-stream"; + } + + auto &blob = data[blobKeys.Blob].AsObject(); + auto blobId = blob[blobKeys.BlobId].AsString(); + auto bytes = m_blobPersistor->ResolveMessage( + std::move(blobId), blob[blobKeys.Offset].AsInt64(), blob[blobKeys.Size].AsInt64()); + + return { + {blobKeys.Type, type}, + {blobKeys.Size, bytes.size()}, + {"bytes", msrn::JSValueArray(bytes.cbegin(), bytes.cend())}}; +} + +#pragma endregion IRequestBodyHandler + +#pragma endregion BlobModuleRequestBodyHandler + +#pragma region BlobModuleResponseHandler + +BlobModuleResponseHandler::BlobModuleResponseHandler(shared_ptr blobPersistor) noexcept + : m_blobPersistor{blobPersistor} {} + +#pragma region IResponseHandler + +bool BlobModuleResponseHandler::Supports(string &responseType) /*override*/ { + return blobKeys.Blob == responseType; +} + +msrn::JSValueObject BlobModuleResponseHandler::ToResponseData(vector &&content) /*override*/ { + return { + {blobKeys.Offset, 0}, + {blobKeys.Size, content.size()}, + {blobKeys.BlobId, m_blobPersistor->StoreMessage(std::move(content))}}; +} + +#pragma endregion IResponseHandler + +#pragma endregion BlobModuleResponseHandler + +} // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/DefaultBlobResource.h b/vnext/Shared/Networking/DefaultBlobResource.h new file mode 100644 index 00000000000..4dfdf5f18aa --- /dev/null +++ b/vnext/Shared/Networking/DefaultBlobResource.h @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "IBlobResource.h" + +#include +#include +#include +#include + +// React Native Windows +#include + +// Boost Libraries +#include + +// Standard Library +#include +#include + +namespace Microsoft::React::Networking { + +class MemoryBlobPersistor final : public IBlobPersistor { + std::unordered_map> m_blobs; + std::mutex m_mutex; + boost::uuids::random_generator m_guidGenerator; + + public: +#pragma region IBlobPersistor + + winrt::array_view ResolveMessage(std::string &&blobId, int64_t offset, int64_t size) override; + + void RemoveMessage(std::string &&blobId) noexcept override; + + void StoreMessage(std::vector &&message, std::string &&blobId) noexcept override; + + std::string StoreMessage(std::vector &&message) noexcept override; + +#pragma endregion IBlobPersistor +}; + +class BlobWebSocketModuleContentHandler final : public IWebSocketModuleContentHandler { + std::unordered_set m_socketIds; + std::mutex m_mutex; + std::shared_ptr m_blobPersistor; + + public: + BlobWebSocketModuleContentHandler(std::shared_ptr blobPersistor) noexcept; + +#pragma region IWebSocketModuleContentHandler + + void ProcessMessage(std::string &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept override; + + void ProcessMessage(std::vector &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept + override; + +#pragma endregion IWebSocketModuleContentHandler + + void Register(int64_t socketID) noexcept; + + void Unregister(int64_t socketID) noexcept; +}; + +class BlobModuleRequestBodyHandler final : public IRequestBodyHandler { + std::shared_ptr m_blobPersistor; + + public: + BlobModuleRequestBodyHandler(std::shared_ptr blobPersistor) noexcept; + +#pragma region IRequestBodyHandler + + bool Supports(winrt::Microsoft::ReactNative::JSValueObject &data) override; + + winrt::Microsoft::ReactNative::JSValueObject ToRequestBody( + winrt::Microsoft::ReactNative::JSValueObject &data, + std::string &contentType) override; + +#pragma endregion IRequestBodyHandler +}; + +class BlobModuleResponseHandler final : public IResponseHandler { + std::shared_ptr m_blobPersistor; + + public: + BlobModuleResponseHandler(std::shared_ptr blobPersistor) noexcept; + +#pragma region IResponseHandler + + bool Supports(std::string &responseType) override; + + winrt::Microsoft::ReactNative::JSValueObject ToResponseData(std::vector &&content) override; + +#pragma endregion IResponseHandler +}; + +class DefaultBlobResource : public IBlobResource, public std::enable_shared_from_this { + std::shared_ptr m_blobPersistor; + std::shared_ptr m_contentHandler; + std::shared_ptr m_requestBodyHandler; + std::shared_ptr m_responseHandler; + winrt::Microsoft::ReactNative::ReactPropertyBag m_propertyBag; + BlobCallbacks m_callbacks; + + public: + DefaultBlobResource( + std::shared_ptr blobPersistor, + std::shared_ptr contentHandler, + std::shared_ptr requestBodyHandler, + std::shared_ptr responseHandler, + winrt::Microsoft::ReactNative::ReactPropertyBag propertyBag); + +#pragma region IBlobResource + + void SendOverSocket(std::string &&blobId, int64_t offset, int64_t size, int64_t socketId) noexcept override; + + void CreateFromParts(winrt::Microsoft::ReactNative::JSValueArray &&parts, std::string &&blobId) noexcept override; + + void Release(std::string &&blobId) noexcept override; + + void AddNetworkingHandler() noexcept override; + + void AddWebSocketHandler(int64_t id) noexcept override; + + void RemoveWebSocketHandler(int64_t id) noexcept override; + + BlobCallbacks &Callbacks() noexcept override; + +#pragma endregion IBlobResource +}; + +} // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/IBlobResource.h b/vnext/Shared/Networking/IBlobResource.h new file mode 100644 index 00000000000..eac3f5d2893 --- /dev/null +++ b/vnext/Shared/Networking/IBlobResource.h @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include + +// React Native Windows +#include + +// Windows API +#include + +// Standard Library +#include +#include +#include + +namespace Microsoft::React::Networking { + +struct IBlobResource { + struct BlobCallbacks { + std::function OnError; + }; + + struct BlobFieldNames { + const char *Blob; + const char *BlobId; + const char *Offset; + const char *Size; + const char *Type; + const char *Data; + }; + + static std::shared_ptr Make(winrt::Windows::Foundation::IInspectable const &inspectableProperties); + + static const BlobFieldNames &FieldNames() noexcept; + + virtual ~IBlobResource() noexcept {} + + virtual void SendOverSocket(std::string &&blobId, int64_t offset, int64_t size, int64_t socketId) noexcept = 0; + + virtual void CreateFromParts(winrt::Microsoft::ReactNative::JSValueArray &&parts, std::string &&blobId) noexcept = 0; + + virtual void Release(std::string &&blobId) noexcept = 0; + + virtual void AddNetworkingHandler() noexcept = 0; + + virtual void AddWebSocketHandler(int64_t id) noexcept = 0; + + virtual void RemoveWebSocketHandler(int64_t id) noexcept = 0; + + virtual BlobCallbacks &Callbacks() noexcept = 0; +}; + +} // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/IHttpResource.h b/vnext/Shared/Networking/IHttpResource.h index c590e91aac7..939169ab277 100644 --- a/vnext/Shared/Networking/IHttpResource.h +++ b/vnext/Shared/Networking/IHttpResource.h @@ -3,8 +3,8 @@ #pragma once -// Folly -#include +// React Native Windows +#include // Windows API #include @@ -20,7 +20,6 @@ namespace Microsoft::React::Networking { struct IHttpResource { typedef std::unordered_map Headers; - // TODO: Implement Form data struct BodyData { enum class Type : size_t { Empty, String, Base64, Uri, Form } Type = Type::Empty; std::string Data; @@ -81,7 +80,7 @@ struct IHttpResource { std::string &&url, int64_t requestId, Headers &&headers, - folly::dynamic &&data, + winrt::Microsoft::ReactNative::JSValueObject &&data, std::string &&responseType, bool useIncrementalUpdates, int64_t timeout, @@ -146,7 +145,9 @@ struct IHttpResource { /// Structured response content payload (i.e. Blob data) /// /// - virtual void SetOnData(std::function &&handler) noexcept = 0; + virtual void SetOnData( + std::function + &&handler) noexcept = 0; /// /// Sets a function to be invoked when a response content increment has been received. diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index 6022450205b..8ff56421dc8 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -23,8 +23,6 @@ #include #include -using folly::dynamic; - using std::function; using std::scoped_lock; using std::shared_ptr; @@ -35,6 +33,7 @@ using std::weak_ptr; using winrt::fire_and_forget; using winrt::hresult_error; using winrt::to_hstring; +using winrt::Microsoft::ReactNative::JSValueObject; using winrt::Windows::Foundation::IAsyncOperation; using winrt::Windows::Foundation::IInspectable; using winrt::Windows::Foundation::Uri; @@ -69,15 +68,15 @@ constexpr char responseTypeBlob[] = "blob"; namespace Microsoft::React::Networking { // May throw winrt::hresult_error -void AttachMultipartHeaders(IHttpContent content, const dynamic &headers) { +void AttachMultipartHeaders(IHttpContent content, const JSValueObject &headers) { HttpMediaTypeHeaderValue contentType{nullptr}; // Headers are generally case-insensitive // https://www.ietf.org/rfc/rfc2616.txt section 4.2 // TODO: Consolidate with PerformRequest's header parsing. - for (auto &header : headers.items()) { - auto &name = header.first.getString(); - auto &value = header.second.getString(); + for (auto &header : headers) { + auto &name = header.first; + auto value = header.second.AsString(); if (boost::iequals(name.c_str(), "Content-Type")) { contentType = HttpMediaTypeHeaderValue::Parse(to_hstring(value)); @@ -147,6 +146,14 @@ IAsyncOperation WinRTHttpResource::CreateRequest( } co_return nullptr; } + } else if (boost::iequals(name.c_str(), "User-Agent")) { + bool success = request.Headers().TryAppendWithoutValidation(to_hstring(name), to_hstring(value)); + if (!success) { + if (self->m_onError) { + self->m_onError(reqArgs->RequestId, "Failed to append User-Agent", false); + } + co_return nullptr; + } } else { try { request.Headers().Append(to_hstring(name), to_hstring(value)); @@ -162,11 +169,11 @@ IAsyncOperation WinRTHttpResource::CreateRequest( // Initialize content IHttpContent content{nullptr}; auto &data = reqArgs->Data; - if (!data.isNull()) { + if (!data.empty()) { auto bodyHandler = self->m_requestBodyHandler.lock(); if (bodyHandler && bodyHandler->Supports(data)) { auto contentTypeString = contentType ? winrt::to_string(contentType.ToString()) : ""; - dynamic blob; + JSValueObject blob; try { blob = bodyHandler->ToRequestBody(data, contentTypeString); } catch (const std::invalid_argument &e) { @@ -175,44 +182,45 @@ IAsyncOperation WinRTHttpResource::CreateRequest( } co_return nullptr; } - auto bytes = blob["bytes"]; + auto &bytes = blob["bytes"].AsArray(); auto byteVector = vector(bytes.size()); for (auto &byte : bytes) { - byteVector.push_back(static_cast(byte.asInt())); + byteVector.push_back(static_cast(byte.AsUInt8())); } auto view = winrt::array_view{byteVector}; auto buffer = CryptographicBuffer::CreateFromByteArray(view); content = HttpBufferContent{std::move(buffer)}; - } else if (!data["string"].isNull()) { - content = HttpStringContent{to_hstring(data["string"].asString())}; - } else if (!data["base64"].empty()) { - auto buffer = CryptographicBuffer::DecodeFromBase64String(to_hstring(data["base64"].asString())); + } else if (data.find("string") != data.cend()) { + content = HttpStringContent{to_hstring(data["string"].AsString())}; + } else if (data.find("base64") != data.cend()) { + auto buffer = CryptographicBuffer::DecodeFromBase64String(to_hstring(data["base64"].AsString())); content = HttpBufferContent{std::move(buffer)}; - } else if (!data["uri"].empty()) { - auto file = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{to_hstring(data["uri"].asString())}); + } else if (data.find("uri") != data.cend()) { + auto file = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{to_hstring(data["uri"].AsString())}); auto stream = co_await file.OpenReadAsync(); content = HttpStreamContent{std::move(stream)}; - } else if (!data["formData"].empty()) { + } else if (data.find("formData") != data.cend()) { winrt::Windows::Web::Http::HttpMultipartFormDataContent multiPartContent; - auto formData = data["formData"]; + auto &formData = data["formData"].AsObject(); // #6046 - Overwriting WinRT's HttpMultipartFormDataContent implicit Content-Type clears the generated boundary contentType = nullptr; for (auto &formDataPart : formData) { IHttpContent formContent{nullptr}; - if (!formDataPart["string"].isNull()) { - formContent = HttpStringContent{to_hstring(formDataPart["string"].asString())}; - } else if (!formDataPart["uri"].empty()) { - auto filePath = to_hstring(formDataPart["uri"].asString()); + auto &itr = formDataPart.second["string"]; + if (!formDataPart.second["string"].IsNull()) { + formContent = HttpStringContent{to_hstring(formDataPart.second["string"].AsString())}; + } else if (!formDataPart.second["uri"].IsNull()) { + auto filePath = to_hstring(formDataPart.second["uri"].AsString()); auto file = co_await StorageFile::GetFileFromPathAsync(filePath); auto stream = co_await file.OpenReadAsync(); formContent = HttpStreamContent{stream}; } if (formContent) { - AttachMultipartHeaders(formContent, formDataPart["headers"]); - multiPartContent.Add(formContent, to_hstring(formDataPart["fieldName"].asString())); + AttachMultipartHeaders(formContent, formDataPart.second["headers"].AsObject()); + multiPartContent.Add(formContent, to_hstring(formDataPart.second["fieldName"].AsString())); } } // foreach form data part @@ -266,7 +274,7 @@ void WinRTHttpResource::SendRequest( string &&url, int64_t requestId, Headers &&headers, - dynamic &&data, + JSValueObject &&data, string &&responseType, bool useIncrementalUpdates, int64_t timeout, @@ -345,10 +353,10 @@ void WinRTHttpResource::SetOnData(function &&handler) noexcept +void WinRTHttpResource::SetOnData(function &&handler) noexcept /*override*/ { - m_onDataDynamic = std::move(handler); + m_onDataObject = std::move(handler); } void WinRTHttpResource::SetOnIncrementalData( @@ -397,7 +405,7 @@ WinRTHttpResource::PerformSendRequest(HttpMethod &&method, Uri &&rtUri, IInspect // Ensure background thread co_await winrt::resume_background(); - auto props = winrt::multi_threaded_map(); + auto props = winrt::single_threaded_map(); props.Insert(L"RequestArgs", coArgs); auto coRequestOp = CreateRequest(std::move(coMethod), std::move(coUri), props); @@ -418,8 +426,8 @@ WinRTHttpResource::PerformSendRequest(HttpMethod &&method, Uri &&rtUri, IInspect try { if (uriHandler->Supports(uri, reqArgs->ResponseType)) { auto blob = uriHandler->Fetch(uri); - if (self->m_onDataDynamic && self->m_onRequestSuccess) { - self->m_onDataDynamic(reqArgs->RequestId, std::move(blob)); + if (self->m_onDataObject && self->m_onRequestSuccess) { + self->m_onDataObject(reqArgs->RequestId, std::move(blob)); self->m_onRequestSuccess(reqArgs->RequestId); } @@ -528,8 +536,8 @@ WinRTHttpResource::PerformSendRequest(HttpMethod &&method, Uri &&rtUri, IInspect auto blob = responseHandler->ToResponseData(std::move(responseData)); - if (self->m_onDataDynamic && self->m_onRequestSuccess) { - self->m_onDataDynamic(reqArgs->RequestId, std::move(blob)); + if (self->m_onDataObject && self->m_onRequestSuccess) { + self->m_onDataObject(reqArgs->RequestId, std::move(blob)); self->m_onRequestSuccess(reqArgs->RequestId); } diff --git a/vnext/Shared/Networking/WinRTHttpResource.h b/vnext/Shared/Networking/WinRTHttpResource.h index 7b18c69c6aa..8a8e19fad18 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.h +++ b/vnext/Shared/Networking/WinRTHttpResource.h @@ -28,7 +28,7 @@ class WinRTHttpResource : public IHttpResource, std::function m_onRequestSuccess; std::function m_onResponse; std::function m_onData; - std::function m_onDataDynamic; + std::function m_onDataObject; std::function m_onError; std::function m_onIncrementalData; @@ -71,7 +71,7 @@ class WinRTHttpResource : public IHttpResource, std::string &&url, int64_t requestId, Headers &&headers, - folly::dynamic &&data, + winrt::Microsoft::ReactNative::JSValueObject &&data, std::string &&responseType, bool useIncrementalUpdates, int64_t timeout, @@ -83,7 +83,8 @@ class WinRTHttpResource : public IHttpResource, void SetOnRequestSuccess(std::function &&handler) noexcept override; void SetOnResponse(std::function &&handler) noexcept override; void SetOnData(std::function &&handler) noexcept override; - void SetOnData(std::function &&handler) noexcept override; + void SetOnData(std::function + &&handler) noexcept override; void SetOnIncrementalData( std::function &&handler) noexcept override; diff --git a/vnext/Shared/Networking/WinRTTypes.h b/vnext/Shared/Networking/WinRTTypes.h index d0bbafc27a0..9c5df0f2afd 100644 --- a/vnext/Shared/Networking/WinRTTypes.h +++ b/vnext/Shared/Networking/WinRTTypes.h @@ -5,8 +5,8 @@ #include "IHttpResource.h" -// Folly -#include +// React Native Windows +#include // Windows API #include @@ -19,7 +19,7 @@ namespace Microsoft::React::Networking { struct RequestArgs : public winrt::implements { int64_t RequestId; IHttpResource::Headers Headers; - folly::dynamic Data; + winrt::Microsoft::ReactNative::JSValueObject Data; bool IncrementalUpdates; bool WithCredentials; std::string ResponseType; diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 5f3f8c71ce8..a54beb1e906 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -146,6 +146,7 @@ true + @@ -179,6 +180,7 @@ + @@ -283,8 +285,11 @@ $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueWriter.idl + + + @@ -296,7 +301,6 @@ - @@ -305,6 +309,9 @@ + + + diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 31ef9a380cc..0c1d2ae64ee 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -254,6 +254,9 @@ + + Source Files + Hermes @@ -263,6 +266,10 @@ + + + Source Files\Networking + @@ -586,9 +593,6 @@ Header Files\Modules - - Header Files\Modules - Header Files\Modules @@ -704,6 +708,15 @@ + + Header Files + + + Header Files + + + Header Files\Fabric\platform\react\renderer\core + Hermes @@ -715,6 +728,18 @@ + + Header Files\Modules + + + Header Files\Networking + + + Header Files\Networking + + + Header Files + diff --git a/vnext/overrides.json b/vnext/overrides.json index baa3e88c2c2..a498f7195cf 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -442,8 +442,10 @@ "baseHash": "daeb879969c322e67efcb9595d6bb171b9dad6d8" }, { - "type": "platform", - "file": "src/Libraries/Network/RCTNetworking.windows.js" + "type": "copy", + "file": "src/Libraries/Network/RCTNetworking.windows.js", + "baseFile": "packages/react-native/Libraries/Network/RCTNetworking.ios.js", + "baseHash": "39fc8e1003b0f6345ec4fea060f455d06939a321" }, { "type": "patch", diff --git a/vnext/src/Libraries/Network/RCTNetworking.windows.js b/vnext/src/Libraries/Network/RCTNetworking.windows.js index aa34b0f6527..4b49b505695 100644 --- a/vnext/src/Libraries/Network/RCTNetworking.windows.js +++ b/vnext/src/Libraries/Network/RCTNetworking.windows.js @@ -1,6 +1,8 @@ /** - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. + * 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. * * @flow strict-local * @format @@ -9,11 +11,10 @@ 'use strict'; import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter'; -const RCTNetworkingNative = - require('../BatchedBridge/NativeModules').Networking; // [Windows] -import {type NativeResponseType} from './XMLHttpRequest'; -import convertRequestBody, {type RequestBody} from './convertRequestBody'; import {type EventSubscription} from '../vendor/emitter/EventEmitter'; +import convertRequestBody, {type RequestBody} from './convertRequestBody'; +import NativeNetworkingIOS from './NativeNetworkingIOS'; +import {type NativeResponseType} from './XMLHttpRequest'; type RCTNetworkingEventDefinitions = $ReadOnly<{ didSendNetworkData: [ @@ -61,11 +62,6 @@ type RCTNetworkingEventDefinitions = $ReadOnly<{ ], }>; -let _requestId = 1; -function generateRequestId(): number { - return _requestId++; -} - const RCTNetworking = { addListener>( eventType: K, @@ -88,13 +84,11 @@ const RCTNetworking = { callback: (requestId: number) => void, withCredentials: boolean, ) { - const requestId = generateRequestId(); const body = convertRequestBody(data); - RCTNetworkingNative.sendRequest( + NativeNetworkingIOS.sendRequest( { method, url, - requestId, data: {...body, trackingName}, headers, responseType, @@ -107,11 +101,11 @@ const RCTNetworking = { }, abortRequest(requestId: number) { - RCTNetworkingNative.abortRequest(requestId); + NativeNetworkingIOS.abortRequest(requestId); }, clearCookies(callback: (result: boolean) => void) { - RCTNetworkingNative.clearCookies(callback); + NativeNetworkingIOS.clearCookies(callback); }, };