From 7cc79735c237a618e21a0b30a507ffdb7a5d0498 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 15:51:15 -0700 Subject: [PATCH 01/23] Implement Origin Policy filter (#9771) * Move all awaitable code into PerformSendRequest * Await coroutine methods * Remove blank lines * Simplify co_return UntrackResponse * Use dynamic body for server internal request * Defne SetOnOptions * Move url before headers in Response type * Add dummy preflight filter * Update x64 exports * Use strand for server IO context * Use HTTP message aliases * Use HTTP message aliases * Do not keep session references Allows dropping server scope in tests. * Comment out server scopes * Use DynamicRequest|Response alias * Format * Use thread vector * Drop outdated boost package validation in ReactCommon * Define experimental write strand * Drop server scope in default test * Also pass server context to sessions * Disable resource in default test * Use Beast example * Remove unused sample code * Create HttpServer as listener wrapper * Pass callbacks down to session * Strong-name response as DynamicResponse * Use DynamicRequest in handle_request * Define HandleRequest as member function * Lambda-based Respond() * #if-out original sample code * Keep count on get: ReaGetSucceeds * Implement lambda_ using std::function * Run context in io thread * Join threads in Stop method * Port send lambda to MS::R::T::HttpServer * Update other basic tests * Ensure Get+Options sequencing * Clean up comments * Use Callbacks() method * Add concurrency argument * Reduce macro usage * Fix default OPTIONS handler * Ensure number of headers * Define ResponseType * Use ResponseWrapper for polymorphism * Define remaining wrapped types (File, String) * Clean up server code * (WIP) add test PreflightSucceeds * catch hresult_error * Use ProcessRequest result in PreformSendReq * Rename test header value to Requested * Propagate orror in ProcessRequest * Rename OPReqFilter to PrototypeReqFilter * Port request filter to WinRT IHttpFilter subtype * Define allowed/forbidden methods and headers * Define MSRN::Networking::OriginPolicyHttpFilter * Move networking types into Shared\Networking folder * Refactor: Move network types to Microsoft::React::Networking * Clean up commented inline * Make OPFilter::SendAsync non const * Remove PrototypeHttpFilter * Temporarily have desk.ITs depend on CppWinRT * Define test OriginPolicyHttpFilterTest::UrlsHaveSameOrigin * Add more same origin tests * Start implementing ValidateRequest * Finish ValidateRequest() * Rename SingleOrigin to SameOrigin * Implement SendPreflightAsync * Fix OP assignment and GetOrigin rendering * Parse Access-Control-Allow-Headers * Done extracting access control values * Use request as argument of ValidatePreflightResponse * clang format * Pass RequestArgs to request properties * Pass RequestArgs to ValidateAllowOrigin * Remove prototype non-WinRT filter * Implement CorsUnsafeNotForbiddenRequestHeaderNames * Test WinRT RequestHeader case sensitivity * Fix ValidateAllowOrigin 4.10.5 * Add HttpOriginPolicyIntegrationTest * Use boost:iequals to compare method names * Add server support for CONNECT and TRACE methods * Make HttpServer port uint16_t * Prelfight only when OP is CORS. - Add test SimpleCorsSameOriginSucceededs - Add test NoCorsCrossOriginPatchSucceededs - Add test NoCorsCrossOriginFetchRequestSucceeds - Add test HTTP Server support for PATCH * Use runtime option Http.StrictScheme * Drop namespace from OriginPolicy * Remove Origin from request heders * Clean includes and usings in WinRTHttpResource.cpp * Update preflight cache issue references (#9770) * Pass origin in IHttpResource constructor * clang format * Prevent nullptr access when iterating preflight request headers * Include request content headers in preflight headers list * Send preflight to original request URL. - Change test origin URL to non-existing http://example.rnw - Have OriginPolicyHttpFilter::SendRequestAsync catch hresult_error to avoid losing info in (...) clause. * Export APIs Set/GetRuntimeOptionString - Switch to class-level static origin Uri. - Define static (global) origin Uri via runtime option "Http.GlobalOrigin". * clang format * Implement TestOriginPolicy to parameterize OP tests * Clean up non-parameterized tests * Use constant for dummy cross origin * Simplify test param constructors * Start implementing ValidateResponse * Add more tests - FullCorsCrossOriginMissingCorsHeadersFails - FullCorsCrossOriginMismatchedCorsHeaderFails * clang format * Change files * Update namespaces in MSRN solution * Move RequestArgs and ResponseType into new header * Implement ExtractAccessControlValues - Validate test result against ServerParams::Response::result * Report specific origin mismatch errors in ValidatePreflightResponse * Declare FullCorsCrossOriginCheckFailsOnPreflightRedirectFails * Add ABI-safe runtime options free functions. - Microsoft_React_SetRuntimeOptionBool - Microsoft_React_SetRuntimeOptionInt - Microsoft_React_SetRuntimeOptionString - Microsoft_React_GetRuntimeOptionBool - Microsoft_React_GetRuntimeOptionInt - Microsoft_React_GetRuntimeOptionString * Drop namespaced GetRuntimeOptionString * Use case-insensitive comparison * Drop newline from error message * Return unmanaged copy in GetRuntimeOptionString * Update FullCorsCrossOriginCheckFailsOnPreflightRedirectFails args * Disallow preflight redirect. See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSExternalRedirectNotAllowed * Replace winrt::get_self with .as * Drop LF from error messages * Use self-managed variable port in tests * Start writing FullCorsCorsCheckFailsOnResponseRedirectFails * Scope disabling autoredirect to preflight request only * Update TODOs with open issues * Compute originAllowed * Add test FullCorsSameOriginToSameOriginRedirectSucceeds * Test FullCorsSameOriginToCrossOriginRedirectSucceeds * Test FullCorsCrossOriginToOriginalOriginRedirectFails * Declare FullCorsCrossOriginToAnotherCrossOriginRedirectSucceeds * Implement OriginPolicyHttpFilter::ConstWcharComparer * Use _wcsicmp instead of boost::iequals in ConstWcharComparer * Correct SimpleCORS value search * Disable FullCorsCrossOriginWithCredentialsSucceeds for now * Rename type alias ResponseType to ResponseOperation * clang format * Handle originPolicy a request property instead of a member variable * Avoid iterating response headers while removing items * Gracefully fail when adding bad request headers - Write remaining integration tests * Use boost::iequals in PerformSendRequest * clang format * Use s_port for redirServer args * Rename TestOriginPolicy to TestOriginPolicyWithRedirect * Temporarily disabled tests - FullCorsCrossOriginToAnotherCrossOriginRedirectWithPreflightSucceeds - pending redirect - FullCorsRequestWithHostHeaderFails - Host request header is probematic * Use std::queue for nonSimpleNames * Finish ValidateResponse * Clean up comments * Add Test ExcludeHttpOnlyCookies * Add tests KeepNonHttpOnlyCookies, RemoveAllCookies * Address TODO comments * Always allow simple-CORS methods in preflight * Avoid auto for loop indexe sused against container .size() * Update Desktop.Test.DLL exports for ARM64 * Skip test FullCorsCrossOriginToAnotherCrossOriginRedirectSucceeds * Disable FullCorsCrossOriginToAnotherCrossOriginRedirectWithPreflightFails * Ignore SimpleCorsForbiddenMethodFails * Ignore SimpleCorsCrossOriginFetchFails * RequestWithProxyAuthorizationHeaderFails * Ignore SimpleCorsSameOriginSucceededs * Ignore NoCorsCrossOriginFetchRequestSucceeds * Revert "Ignore NoCorsCrossOriginFetchRequestSucceeds" This reverts commit b5445fb5af85b428c623ac336b17f6e16bba7f1b. * Revert "Ignore SimpleCorsSameOriginSucceededs" This reverts commit ab75c3737301461894d30008256c92e0a5ba5adc. * Revert "RequestWithProxyAuthorizationHeaderFails" This reverts commit 70148b17de7daa4ed62c3ec115bdbd7c869e7dc5. * Revert "Ignore SimpleCorsCrossOriginFetchFails" This reverts commit 982e4508b9597fc82bde28130ebeaf15b06fcec2. * Revert "Ignore SimpleCorsForbiddenMethodFails" This reverts commit 869bda9782dad9b9b7d8e05d4c9cb689ec438617. * Revert "Disable FullCorsCrossOriginToAnotherCrossOriginRedirectWithPreflightFails" This reverts commit e9e178a07ad1fd6fced627f23681c69e3866eed6. * Revert "Skip test FullCorsCrossOriginToAnotherCrossOriginRedirectSucceeds" This reverts commit 6688e7dce57e509b8865f181fd00355be59a2e4b. * Skip OP integration tests * Empty commit * Use scoped_lock for runtime options * Testing signature * Have C++ Rt Option functions call ABI-safe ones * Ensure different ports for each netwk test class * Remove remaining hard-coded ports from WS tests * Use ABI-safe callback for GetRtOptString * Only insert boolean rt options when true * Use static variable for port in HttpResourceIntegrationTest * Add HttpResourceIntegrationTest::SimpleRedirectSucceeds * Move C++ Rt Optio APIs to new header CppRuntimeOptions.h * Implement internal Set/GetRuntimeOptionString * clang format * Rename Microsoft_React_* functions to MicrosoftReact* * Update nuspec --- vnext/Desktop.DLL/react-native-win32.x86.def | 1 + .../DesktopTestRunner.cpp | 3 + .../HttpOriginPolicyIntegrationTest.cpp | 10 +- .../HttpResourceIntegrationTests.cpp | 25 +- vnext/Shared/Modules/HttpModule.cpp | 255 +++++----- vnext/Shared/Modules/WebSocketModule.cpp | 439 +++++++++--------- vnext/Shared/Modules/WebSocketModule.h | 140 +++--- vnext/Shared/Networking/IHttpResource.h | 52 +-- vnext/Shared/Networking/WinRTHttpResource.cpp | 220 ++------- vnext/Shared/Networking/WinRTHttpResource.h | 80 ++-- vnext/Shared/Networking/WinRTTypes.h | 27 +- vnext/Shared/RuntimeOptions.cpp | 94 ++-- vnext/Shared/RuntimeOptions.h | 25 +- vnext/Shared/Shared.vcxitems.filters | 39 -- 14 files changed, 589 insertions(+), 821 deletions(-) diff --git a/vnext/Desktop.DLL/react-native-win32.x86.def b/vnext/Desktop.DLL/react-native-win32.x86.def index 891cb536520..2c4eed9cda3 100644 --- a/vnext/Desktop.DLL/react-native-win32.x86.def +++ b/vnext/Desktop.DLL/react-native-win32.x86.def @@ -56,6 +56,7 @@ EXPORTS ?GetRuntimeOptionString@React@Microsoft@@YA?BV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@ABV34@@Z ?makeChakraRuntime@JSI@Microsoft@@YG?AV?$unique_ptr@VRuntime@jsi@facebook@@U?$default_delete@VRuntime@jsi@facebook@@@std@@@std@@$$QAUChakraRuntimeArgs@12@@Z ?CreateTimingModule@react@facebook@@YG?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@ABV?$shared_ptr@VMessageQueueThread@react@facebook@@@4@@Z +??0WebSocketModule@React@Microsoft@@QAE@XZ ?Make@IHttpResource@Networking@React@Microsoft@@SG?AV?$shared_ptr@UIHttpResource@Networking@React@Microsoft@@@std@@XZ ??0NetworkingModule@React@Microsoft@@QAE@XZ ?MakeJSQueueThread@ReactNative@Microsoft@@YG?AV?$shared_ptr@VMessageQueueThread@react@facebook@@@std@@XZ diff --git a/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp b/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp index b1ad4b1540b..db877e5b9eb 100644 --- a/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp +++ b/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp @@ -3,6 +3,9 @@ #include +#include +#include +#include #include #include #include "ChakraRuntimeHolder.h" diff --git a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp index 640383f71e6..ce9fb266d61 100644 --- a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp +++ b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp @@ -142,9 +142,8 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) resource->SendRequest( string{http::to_string(clientArgs.Method).data()}, string{server1Args.Url}, - 0, /*requestId*/ std::move(clientArgs.RequestHeaders), - {}, /*data*/ + { IHttpResource::BodyData::Type::String, "REQUEST_CONTENT" }, "text", false, /*useIncrementalUpdates*/ 1000, /*timeout*/ @@ -196,9 +195,8 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) resource->SendRequest( string{http::to_string(clientArgs.Method).data()}, string{serverArgs.Url}, - 0, /*requestId*/ std::move(clientArgs.RequestHeaders), - {}, /*data*/ + { IHttpResource::BodyData::Type::String, "REQUEST_CONTENT" }, "text", false, /*useIncrementalUpdates*/ 1000, /*timeout*/ @@ -292,12 +290,10 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) resource->SendRequest( "TRACE", url, - 0, /*requestId*/ { {"ValidHeader", "AnyValue"} }, - {}, /*data*/ - //{} /*bodyData*/, + {} /*bodyData*/, "text", false /*useIncrementalUpdates*/, 1000 /*timeout*/, diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 2d2e7dac901..d0778b9bb14 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -73,9 +73,8 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "GET", std::move(url), - 0, /*requestId*/ - {}, /*header*/ - {}, /*data*/ + {} /*header*/, + {} /*bodyData*/, "text", false, 1000 /*timeout*/, @@ -129,14 +128,13 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "GET", std::move(url), - 0, /*requestId*/ { {"Content-Type", "application/json"}, {"Content-Encoding", "ASCII"}, {"name3", "value3"}, {"name4", "value4"}, }, - {}, /*data*/ + {} /*bodyData*/, "text", false, 1000 /*timeout*/, @@ -240,9 +238,8 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "OPTIONS", string{url}, - 0, /*requestId*/ - {}, /*headers*/ - {}, /*data*/ + {} /*headers*/, + {} /*bodyData*/, "text", false, 1000 /*timeout*/, @@ -251,9 +248,8 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "GET", std::move(url), - 0, /*requestId*/ - {}, /*headers*/ - {}, /*data*/ + {} /*headers*/, + {} /*bodyData*/, "text", false, 1000 /*timeout*/, @@ -334,9 +330,8 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "GET", std::move(url), - 0, /*requestId*/ - {}, /*headers*/ - {}, /*data*/ + {} /*headers*/, + {} /*bodyData*/, "text", false, /*useIncrementalUpdates*/ 1000 /*timeout*/, @@ -357,3 +352,5 @@ TEST_CLASS (HttpResourceIntegrationTest) { }; /*static*/ uint16_t HttpResourceIntegrationTest::s_port = 4444; + +/*static*/ uint16_t HttpResourceIntegrationTest::s_port = 4444; diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 0c9f2947af2..aa18300a97f 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -25,177 +25,176 @@ using winrt::Windows::Foundation::IInspectable; namespace { -using Microsoft::React::Modules::SendEvent; -using Microsoft::React::Networking::IHttpResource; + using Microsoft::React::Networking::IHttpResource; -constexpr char moduleName[] = "Networking"; + constexpr char moduleName[] = "Networking"; -// React event names -constexpr char completedResponse[] = "didCompleteNetworkResponse"; -constexpr char receivedResponse[] = "didReceiveNetworkResponse"; -constexpr char receivedData[] = "didReceiveNetworkData"; -constexpr char receivedDataProgress[] = "didReceiveNetworkDataProgress"; + // React event names + constexpr char completedResponse[] = "didCompleteNetworkResponse"; + constexpr char receivedResponse[] = "didReceiveNetworkResponse"; + constexpr char receivedData[] = "didReceiveNetworkData"; + constexpr char receivedDataProgress[] = "didReceiveNetworkDataProgress"; -static void SetUpHttpResource( + static void SetUpHttpResource( shared_ptr resource, weak_ptr weakReactInstance, - IInspectable &inspectableProperties) { - resource->SetOnRequestSuccess([weakReactInstance](int64_t requestId) { - auto args = dynamic::array(requestId); + IInspectable& inspectableProperties) { + resource->SetOnRequestSuccess([weakReactInstance](int64_t requestId) { + auto args = dynamic::array(requestId); - SendEvent(weakReactInstance, completedResponse, std::move(args)); - }); + SendEvent(weakReactInstance, completedResponse, std::move(args)); + }); - resource->SetOnResponse([weakReactInstance](int64_t requestId, IHttpResource::Response &&response) { - dynamic headers = dynamic::object(); - for (auto &header : response.Headers) { - headers[header.first] = header.second; - } + resource->SetOnResponse([weakReactInstance](int64_t requestId, IHttpResource::Response&& response) { + dynamic headers = dynamic::object(); + for (auto& header : response.Headers) { + headers[header.first] = header.second; + } - // TODO: Test response content. - dynamic args = dynamic::array(requestId, response.StatusCode, headers, response.Url); + // TODO: Test response content. + dynamic args = dynamic::array(requestId, response.StatusCode, headers, response.Url); - SendEvent(weakReactInstance, receivedResponse, std::move(args)); - }); + SendEvent(weakReactInstance, receivedResponse, std::move(args)); + }); - resource->SetOnData([weakReactInstance](int64_t requestId, string &&responseData) { - SendEvent(weakReactInstance, receivedData, dynamic::array(requestId, std::move(responseData))); + resource->SetOnData([weakReactInstance](int64_t requestId, string&& responseData) { + SendEvent(weakReactInstance, receivedData, dynamic::array(requestId, std::move(responseData))); - // TODO: Move into separate method IF not executed right after onData() - SendEvent(weakReactInstance, completedResponse, dynamic::array(requestId)); - }); + // TODO: Move into separate method IF not executed right after onData() + SendEvent(weakReactInstance, completedResponse, dynamic::array(requestId)); + }); - // 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))); - }; - resource->SetOnData(std::move(onDataDynamic)); + // 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))); + }; + resource->SetOnData(std::move(onDataDynamic)); - resource->SetOnError([weakReactInstance](int64_t requestId, string &&message) { - dynamic args = dynamic::array(requestId, std::move(message)); - // TODO: isTimeout errorArgs.push_back(true); + resource->SetOnError([weakReactInstance](int64_t requestId, string&& message) { + dynamic args = dynamic::array(requestId, std::move(message)); + // TODO: isTimeout errorArgs.push_back(true); - SendEvent(weakReactInstance, completedResponse, std::move(args)); - }); -} + SendEvent(weakReactInstance, completedResponse, std::move(args)); + }); + } } // namespace namespace Microsoft::React { -HttpModule::HttpModule(IInspectable const &inspectableProperties) noexcept - : m_holder{std::make_shared()}, - m_inspectableProperties{inspectableProperties}, - m_resource{IHttpResource::Make(inspectableProperties)} { - m_holder->Module = this; -} + HttpModule::HttpModule(IInspectable const& inspectableProperties) noexcept + : m_holder{ std::make_shared() }, + m_inspectableProperties{ inspectableProperties }, + m_resource{ IHttpResource::Make(inspectableProperties) } { + m_holder->Module = this; + } -HttpModule::~HttpModule() noexcept /*override*/ { - m_holder->Module = nullptr; -} + HttpModule::~HttpModule() noexcept /*override*/ { + m_holder->Module = nullptr; + } #pragma region CxxModule -string HttpModule::getName() /*override*/ { - return moduleName; -} + string HttpModule::getName() /*override*/ { + return moduleName; + } -std::map HttpModule::getConstants() { - return {}; -} + std::map HttpModule::getConstants() { + return {}; + } -// clang-format off -std::vector HttpModule::getMethods() { + // clang-format off + std::vector HttpModule::getMethods() { - return - { + return { - "sendRequest", - [weakHolder = weak_ptr(m_holder)](dynamic args, Callback cxxCallback) { - auto holder = weakHolder.lock(); - if (!holder) { - return; - } - - auto resource = holder->Module->m_resource; - if (!holder->Module->m_isResourceSetup) + "sendRequest", + [weakHolder = weak_ptr(m_holder)](dynamic args, Callback cxxCallback) { - SetUpHttpResource(resource, holder->Module->getInstance(), holder->Module->m_inspectableProperties); - holder->Module->m_isResourceSetup = true; - } + auto holder = weakHolder.lock(); + if (!holder) { + return; + } - auto params = facebook::xplat::jsArgAsObject(args, 0); - IHttpResource::Headers headers; - for (auto& header : params["headers"].items()) { - headers.emplace(header.first.getString(), header.second.getString()); - } + auto resource = holder->Module->m_resource; + if (!holder->Module->m_isResourceSetup) + { + SetUpHttpResource(resource, holder->Module->getInstance(), holder->Module->m_inspectableProperties); + holder->Module->m_isResourceSetup = true; + } - resource->SendRequest( - params["method"].asString(), - params["url"].asString(), - params["requestId"].asInt(), - std::move(headers), - std::move(params["data"]), - params["responseType"].asString(), - params["incrementalUpdates"].asBool(), - static_cast(params["timeout"].asDouble()), - params["withCredentials"].asBool(), - [cxxCallback = std::move(cxxCallback)](int64_t requestId) { - cxxCallback({requestId}); + auto params = facebook::xplat::jsArgAsObject(args, 0); + IHttpResource::Headers headers; + for (auto& header : params["headers"].items()) { + headers.emplace(header.first.getString(), header.second.getString()); } - ); - } - }, - { - "abortRequest", - [weakHolder = weak_ptr(m_holder)](dynamic args) + + resource->SendRequest( + params["method"].asString(), + params["url"].asString(), + params["requestId"].asInt(), + std::move(headers), + std::move(params["data"]), + params["responseType"].asString(), + params["incrementalUpdates"].asBool(), + static_cast(params["timeout"].asDouble()), + params["withCredentials"].asBool(), + [cxxCallback = std::move(cxxCallback)](int64_t requestId) { + cxxCallback({requestId}); + } + ); + } + }, { - auto holder = weakHolder.lock(); - if (!holder) + "abortRequest", + [weakHolder = weak_ptr(m_holder)](dynamic args) { - return; - } + auto holder = weakHolder.lock(); + if (!holder) + { + return; + } - auto resource = holder->Module->m_resource; - if (!holder->Module->m_isResourceSetup) - { - SetUpHttpResource(resource, holder->Module->getInstance(), holder->Module->m_inspectableProperties); - holder->Module->m_isResourceSetup = true; - } + auto resource = holder->Module->m_resource; + if (!holder->Module->m_isResourceSetup) + { + SetUpHttpResource(resource, holder->Module->getInstance(), holder->Module->m_inspectableProperties); + holder->Module->m_isResourceSetup = true; + } - resource->AbortRequest(facebook::xplat::jsArgAsInt(args, 0)); - } - }, - { - "clearCookies", - [weakHolder = weak_ptr(m_holder)](dynamic args) + resource->AbortRequest(facebook::xplat::jsArgAsInt(args, 0)); + } + }, { - auto holder = weakHolder.lock(); - if (!holder) + "clearCookies", + [weakHolder = weak_ptr(m_holder)](dynamic args) { - return; - } + auto holder = weakHolder.lock(); + if (!holder) + { + return; + } - auto resource = holder->Module->m_resource; - if (!holder->Module->m_isResourceSetup) - { - SetUpHttpResource(resource, holder->Module->getInstance(), holder->Module->m_inspectableProperties); - holder->Module->m_isResourceSetup = true; - } + auto resource = holder->Module->m_resource; + if (!holder->Module->m_isResourceSetup) + { + SetUpHttpResource(resource, holder->Module->getInstance(), holder->Module->m_inspectableProperties); + holder->Module->m_isResourceSetup = true; + } - resource->ClearCookies(); + resource->ClearCookies(); + } } - } - }; -} -// clang-format on + }; + } + // clang-format on #pragma endregion CxxModule -/*extern*/ const char *GetHttpModuleName() noexcept { - return moduleName; -} + /*extern*/ const char* GetHttpModuleName() noexcept { + return moduleName; + } } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/WebSocketModule.cpp b/vnext/Shared/Modules/WebSocketModule.cpp index 2ccb180e26e..bec9290dacb 100644 --- a/vnext/Shared/Modules/WebSocketModule.cpp +++ b/vnext/Shared/Modules/WebSocketModule.cpp @@ -37,291 +37,294 @@ using winrt::Windows::Foundation::IInspectable; using winrt::Windows::Security::Cryptography::CryptographicBuffer; namespace { -using Microsoft::React::IWebSocketModuleProxy; -using Microsoft::React::WebSocketModule; -using Microsoft::React::Modules::SendEvent; -using Microsoft::React::Networking::IWebSocketResource; + using Microsoft::React::WebSocketModule; + using Microsoft::React::Networking::IWebSocketResource; -constexpr char moduleName[] = "WebSocketModule"; + constexpr char moduleName[] = "WebSocketModule"; -static shared_ptr -GetOrCreateWebSocket(int64_t id, string &&url, weak_ptr weakState) { - auto state = weakState.lock(); - if (!state) { - return nullptr; - } - - auto itr = state->ResourceMap.find(id); - if (itr == state->ResourceMap.end()) { - if (!state->Module) { + static shared_ptr + GetOrCreateWebSocket(int64_t id, string&& url, weak_ptr weakState) { + auto state = weakState.lock(); + if (!state) { return nullptr; } - auto weakInstance = state->Module->getInstance(); - shared_ptr ws; - try { - ws = state->ResourceFactory(std::move(url)); - } catch (const winrt::hresult_error &e) { - std::stringstream ss; - ss << "[" << std::hex << std::showbase << std::setw(8) << static_cast(e.code()) << "] " - << winrt::to_string(e.message()); + auto itr = state->ResourceMap.find(id); + if (itr == state->ResourceMap.end()) { + if (!state->Module) { + return nullptr; + } + auto weakInstance = state->Module->getInstance(); + + shared_ptr ws; + try { + ws = state->ResourceFactory(std::move(url)); + } + catch (const winrt::hresult_error& e) { + std::stringstream ss; + ss << "[" << std::hex << std::showbase << std::setw(8) << static_cast(e.code()) << "] " + << winrt::to_string(e.message()); - SendEvent(weakInstance, "webSocketFailed", dynamic::object("id", id)("message", std::move(ss.str()))); + SendEvent(weakInstance, "webSocketFailed", dynamic::object("id", id)("message", std::move(ss.str()))); - return nullptr; - } catch (const std::exception &e) { - SendEvent(weakInstance, "webSocketFailed", dynamic::object("id", id)("message", e.what())); + return nullptr; + } + catch (const std::exception& e) { + SendEvent(weakInstance, "webSocketFailed", dynamic::object("id", id)("message", e.what())); - return nullptr; - } catch (...) { - SendEvent( + return nullptr; + } + catch (...) { + SendEvent( weakInstance, "webSocketFailed", dynamic::object("id", id)("message", "Unidentified error creating IWebSocketResource")); - return nullptr; - } + return nullptr; + } + + ws->SetOnError([id, weakInstance](const IWebSocketResource::Error& err) { + auto strongInstance = weakInstance.lock(); + if (!strongInstance) + return; + + auto errorObj = dynamic::object("id", id)("message", err.Message); + SendEvent(weakInstance, "websocketFailed", std::move(errorObj)); + }); + ws->SetOnConnect([id, weakInstance]() { + auto strongInstance = weakInstance.lock(); + if (!strongInstance) + return; - ws->SetOnError([id, weakInstance](const IWebSocketResource::Error &err) { - auto strongInstance = weakInstance.lock(); - if (!strongInstance) - return; - - auto errorObj = dynamic::object("id", id)("message", err.Message); - SendEvent(weakInstance, "websocketFailed", std::move(errorObj)); - }); - ws->SetOnConnect([id, weakInstance]() { - auto strongInstance = weakInstance.lock(); - if (!strongInstance) - return; - - auto args = dynamic::object("id", id); - SendEvent(weakInstance, "websocketOpen", std::move(args)); - }); - ws->SetOnMessage( - [id, weakInstance, propBag = ReactPropertyBag{state->InspectableProps.try_as()}]( - size_t length, const string &message, bool isBinary) { - auto strongInstance = weakInstance.lock(); - if (!strongInstance) - return; - - dynamic args = dynamic::object("id", id)("type", isBinary ? "binary" : "text"); - shared_ptr contentHandler; - auto propId = ReactPropertyId>>{ - L"BlobModule.ContentHandler"}; - 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 = std::vector(arr.begin(), arr.end()); - - contentHandler->ProcessMessage(std::move(data), args); - } else { - contentHandler->ProcessMessage(string{message}, args); + auto args = dynamic::object("id", id); + SendEvent(weakInstance, "websocketOpen", std::move(args)); + }); + ws->SetOnMessage( + [id, weakInstance, propBag = ReactPropertyBag{ state->InspectableProps.try_as() }]( + size_t length, const string& message, bool isBinary) { + auto strongInstance = weakInstance.lock(); + if (!strongInstance) + return; + + dynamic args = dynamic::object("id", id)("type", isBinary ? "binary" : "text"); + shared_ptr contentHandler; + auto propId = ReactPropertyId>>{ + L"BlobModule.ContentHandler" }; + 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 = std::vector(arr.begin(), arr.end()); + + contentHandler->ProcessMessage(std::move(data), args); + } + else { + contentHandler->ProcessMessage(string{ message }, args); + } } - } else { - args["data"] = message; - } + else { + args["data"] = message; + } + + SendEvent(weakInstance, "websocketMessage", std::move(args)); + }); + ws->SetOnClose([id, weakInstance](IWebSocketResource::CloseCode code, const string& reason) { + auto strongInstance = weakInstance.lock(); + if (!strongInstance) + return; - SendEvent(weakInstance, "websocketMessage", std::move(args)); + auto args = dynamic::object("id", id)("code", static_cast(code))("reason", reason); + SendEvent(weakInstance, "websocketClosed", std::move(args)); }); - ws->SetOnClose([id, weakInstance](IWebSocketResource::CloseCode code, const string &reason) { - auto strongInstance = weakInstance.lock(); - if (!strongInstance) - return; - auto args = dynamic::object("id", id)("code", static_cast(code))("reason", reason); - SendEvent(weakInstance, "websocketClosed", std::move(args)); - }); + state->ResourceMap.emplace(id, ws); + return ws; + } - state->ResourceMap.emplace(id, ws); - return ws; + return itr->second; } - return itr->second; -} - } // anonymous namespace namespace Microsoft::React { #pragma region WebSocketModule -WebSocketModule::WebSocketModule(winrt::Windows::Foundation::IInspectable const &inspectableProperties) - : m_sharedState{std::make_shared()}, - m_proxy{std::make_shared(inspectableProperties)} { - m_sharedState->ResourceFactory = [](string &&url) { return IWebSocketResource::Make(); }; - m_sharedState->Module = this; - m_sharedState->InspectableProps = inspectableProperties; - - auto propBag = ReactPropertyBag{m_sharedState->InspectableProps.try_as()}; - - auto proxyPropId = ReactPropertyId>>{L"WebSocketModule.Proxy"}; - auto proxy = weak_ptr{m_proxy}; - propBag.Set(proxyPropId, std::move(proxy)); - - auto statePropId = ReactPropertyId>>{L"WebSocketModule.SharedState"}; - auto state = weak_ptr{m_sharedState}; - propBag.Set(statePropId, std::move(state)); -} - -WebSocketModule::~WebSocketModule() noexcept /*override*/ { - m_sharedState->Module = nullptr; -} - -void WebSocketModule::SetResourceFactory( - std::function(const string &)> &&resourceFactory) { - m_sharedState->ResourceFactory = std::move(resourceFactory); -} - -string WebSocketModule::getName() { - return moduleName; -} - -std::map WebSocketModule::getConstants() { - return {}; -} - -// clang-format off -std::vector WebSocketModule::getMethods() -{ - return + WebSocketModule::WebSocketModule(winrt::Windows::Foundation::IInspectable const& inspectableProperties) + : m_sharedState{ std::make_shared() }, + m_proxy{ std::make_shared(inspectableProperties) } { + m_sharedState->ResourceFactory = [](string&& url) { return IWebSocketResource::Make(); }; + m_sharedState->Module = this; + m_sharedState->InspectableProps = inspectableProperties; + + auto propBag = ReactPropertyBag{ m_sharedState->InspectableProps.try_as() }; + + auto proxyPropId = ReactPropertyId>>{ L"WebSocketModule.Proxy" }; + auto proxy = weak_ptr{ m_proxy }; + propBag.Set(proxyPropId, std::move(proxy)); + + auto statePropId = ReactPropertyId>>{ L"WebSocketModule.SharedState" }; + auto state = weak_ptr{ m_sharedState }; + propBag.Set(statePropId, std::move(state)); + } + + WebSocketModule::~WebSocketModule() noexcept /*override*/ { + m_sharedState->Module = nullptr; + } + + void WebSocketModule::SetResourceFactory( + std::function(const string&)>&& resourceFactory) { + m_sharedState->ResourceFactory = std::move(resourceFactory); + } + + string WebSocketModule::getName() { + return moduleName; + } + + std::map WebSocketModule::getConstants() { + return {}; + } + + // clang-format off + std::vector WebSocketModule::getMethods() { + return { - "connect", - [weakState = weak_ptr(m_sharedState)](dynamic args) // const string& url, dynamic protocols, dynamic options, int64_t id { - IWebSocketResource::Protocols protocols; - dynamic protocolsDynamic = jsArgAsDynamic(args, 1); - if (!protocolsDynamic.empty()) + "connect", + [weakState = weak_ptr(m_sharedState)](dynamic args) // const string& url, dynamic protocols, dynamic options, int64_t id { - for (const auto& protocol : protocolsDynamic) + IWebSocketResource::Protocols protocols; + dynamic protocolsDynamic = jsArgAsDynamic(args, 1); + if (!protocolsDynamic.empty()) { - protocols.push_back(protocol.getString()); + for (const auto& protocol : protocolsDynamic) + { + protocols.push_back(protocol.getString()); + } } - } - IWebSocketResource::Options options; - dynamic optionsDynamic = jsArgAsDynamic(args, 2); - if (!optionsDynamic.empty() && optionsDynamic.count("headers") != 0) - { - const auto& headersDynamic = optionsDynamic["headers"]; - for (const auto& header : headersDynamic.items()) + IWebSocketResource::Options options; + dynamic optionsDynamic = jsArgAsDynamic(args, 2); + if (!optionsDynamic.empty() && optionsDynamic.count("headers") != 0) { - options.emplace(winrt::to_hstring(header.first.getString()), header.second.getString()); + const auto& headersDynamic = optionsDynamic["headers"]; + for (const auto& header : headersDynamic.items()) + { + options.emplace(winrt::to_hstring(header.first.getString()), header.second.getString()); + } } - } - weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 3), jsArgAsString(args, 0), weakState); - if (auto sharedWs = weakWs.lock()) - { - sharedWs->Connect(jsArgAsString(args, 0), protocols, options); - } - } - }, - { - "close", - [weakState = weak_ptr(m_sharedState)](dynamic args) // [int64_t code, string reason,] int64_t id - { - // See react-native\Libraries\WebSocket\WebSocket.js:_close - if (args.size() == 3) // WebSocketModule.close(statusCode, closeReason, this._socketId); - { - weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 2), {}, weakState); + weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 3), jsArgAsString(args, 0), weakState); if (auto sharedWs = weakWs.lock()) { - sharedWs->Close(static_cast(jsArgAsInt(args, 0)), jsArgAsString(args, 1)); + sharedWs->Connect(jsArgAsString(args, 0), protocols, options); } } - else if (args.size() == 1) // WebSocketModule.close(this._socketId); + }, + { + "close", + [weakState = weak_ptr(m_sharedState)](dynamic args) // [int64_t code, string reason,] int64_t id { - weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 0), {}, weakState); - if (auto sharedWs = weakWs.lock()) + // See react-native\Libraries\WebSocket\WebSocket.js:_close + if (args.size() == 3) // WebSocketModule.close(statusCode, closeReason, this._socketId); { - sharedWs->Close(IWebSocketResource::CloseCode::Normal, {}); + weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 2), {}, weakState); + if (auto sharedWs = weakWs.lock()) + { + sharedWs->Close(static_cast(jsArgAsInt(args, 0)), jsArgAsString(args, 1)); + } } - } - else - { - auto state = weakState.lock(); - if (state && state->Module) { - auto errorObj = dynamic::object("id", -1)("message", "Incorrect number of parameters"); - SendEvent(state->Module->getInstance(), "websocketFailed", std::move(errorObj)); + else if (args.size() == 1) // WebSocketModule.close(this._socketId); + { + weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 0), {}, weakState); + if (auto sharedWs = weakWs.lock()) + { + sharedWs->Close(IWebSocketResource::CloseCode::Normal, {}); + } + } + else + { + auto state = weakState.lock(); + if (state && state->Module) { + auto errorObj = dynamic::object("id", -1)("message", "Incorrect number of parameters"); + SendEvent(state->Module->getInstance(), "websocketFailed", std::move(errorObj)); + } } } - } - }, - { - "send", - [weakState = weak_ptr(m_sharedState)](dynamic args) // const string& message, int64_t id + }, { - weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 1), {}, weakState); - if (auto sharedWs = weakWs.lock()) + "send", + [weakState = weak_ptr(m_sharedState)](dynamic args) // const string& message, int64_t id { - sharedWs->Send(jsArgAsString(args, 0)); + weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 1), {}, weakState); + if (auto sharedWs = weakWs.lock()) + { + sharedWs->Send(jsArgAsString(args, 0)); + } } - } - }, - { - "sendBinary", - [weakState = weak_ptr(m_sharedState)](dynamic args) // const string& base64String, int64_t id + }, { - weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 1), {}, weakState); - if (auto sharedWs = weakWs.lock()) + "sendBinary", + [weakState = weak_ptr(m_sharedState)](dynamic args) // const string& base64String, int64_t id { - sharedWs->SendBinary(jsArgAsString(args, 0)); + weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 1), {}, weakState); + if (auto sharedWs = weakWs.lock()) + { + sharedWs->SendBinary(jsArgAsString(args, 0)); + } } - } - }, - { - "ping", - [weakState = weak_ptr(m_sharedState)](dynamic args) // int64_t id + }, { - weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 0), {}, weakState); - if (auto sharedWs = weakWs.lock()) + "ping", + [weakState = weak_ptr(m_sharedState)](dynamic args) // int64_t id { - sharedWs->Ping(); + weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 0), {}, weakState); + if (auto sharedWs = weakWs.lock()) + { + sharedWs->Ping(); + } } } - } - }; -} // getMethods -// clang-format on + }; + } // getMethods + // clang-format on #pragma endregion WebSocketModule #pragma region WebSocketModuleProxy -WebSocketModuleProxy::WebSocketModuleProxy(IInspectable const &inspectableProperties) noexcept - : m_inspectableProps{inspectableProperties} {} + WebSocketModuleProxy::WebSocketModuleProxy(IInspectable const& inspectableProperties) noexcept + : m_inspectableProps{ inspectableProperties } {} -void WebSocketModuleProxy::SendBinary(std::string &&base64String, int64_t id) noexcept /*override*/ { - auto propBag = ReactPropertyBag{m_inspectableProps.try_as()}; - auto sharedPropId = - ReactPropertyId>>{L"WebSocketModule.SharedState"}; - auto state = propBag.Get(sharedPropId).Value(); + void WebSocketModuleProxy::SendBinary(std::string&& base64String, int64_t id) noexcept /*override*/ { + auto propBag = ReactPropertyBag{ m_inspectableProps.try_as() }; + auto sharedPropId = + ReactPropertyId>>{ L"WebSocketModule.SharedState" }; + auto state = propBag.Get(sharedPropId).Value(); - weak_ptr weakWs = GetOrCreateWebSocket(id, {}, std::move(state)); - if (auto sharedWs = weakWs.lock()) { - sharedWs->SendBinary(std::move(base64String)); + weak_ptr weakWs = GetOrCreateWebSocket(id, {}, std::move(state)); + if (auto sharedWs = weakWs.lock()) { + sharedWs->SendBinary(std::move(base64String)); + } } -} #pragma endregion WebSocketModuleProxy -/*extern*/ const char *GetWebSocketModuleName() noexcept { - return moduleName; -} + /*extern*/ const char* GetWebSocketModuleName() noexcept { + return moduleName; + } -/*extern*/ std::unique_ptr CreateWebSocketModule( - IInspectable const &inspectableProperties) noexcept { - if (auto properties = inspectableProperties.try_as()) - return std::make_unique(properties); + /*extern*/ std::unique_ptr CreateWebSocketModule( + IInspectable const& inspectableProperties) noexcept { + if (auto properties = inspectableProperties.try_as()) + return std::make_unique(properties); - return nullptr; -} + return nullptr; + } } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/WebSocketModule.h b/vnext/Shared/Modules/WebSocketModule.h index 5a10ac5990a..ea1b7e56bc6 100644 --- a/vnext/Shared/Modules/WebSocketModule.h +++ b/vnext/Shared/Modules/WebSocketModule.h @@ -3,103 +3,99 @@ #pragma once -#include #include // React Native #include -// Windows API -#include - namespace Microsoft::React { -class WebSocketModuleProxy final : public IWebSocketModuleProxy { - // Property bag high level reference. - winrt::Windows::Foundation::IInspectable m_inspectableProps; + class WebSocketModuleProxy final : public IWebSocketModuleProxy { + // Property bag high level reference. + winrt::Windows::Foundation::IInspectable m_inspectableProps; - public: - WebSocketModuleProxy(winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept; + public: + WebSocketModuleProxy(winrt::Windows::Foundation::IInspectable const& inspectableProperties) noexcept; #pragma region IWebSocketModuleProxy - void SendBinary(std::string &&base64String, int64_t id) noexcept override; + void SendBinary(std::string&& base64String, int64_t id) noexcept override; #pragma endregion -}; + }; + + /// + /// Realizes NativeModules projection. + /// See react-native/Libraries/WebSocket/WebSocket.js + /// + class WebSocketModule : public facebook::xplat::module::CxxModule{ + public: + enum MethodId { Connect = 0, Close = 1, Send = 2, SendBinary = 3, Ping = 4, SIZE = 5 }; + + WebSocketModule(winrt::Windows::Foundation::IInspectable const& inspectableProperties); + + ~WebSocketModule() noexcept override; -/// -/// Realizes NativeModules projection. -/// See react-native/Libraries/WebSocket/WebSocket.js -/// -class WebSocketModule : public facebook::xplat::module::CxxModule { - public: - enum MethodId { Connect = 0, Close = 1, Send = 2, SendBinary = 3, Ping = 4, SIZE = 5 }; + struct SharedState { + /// + /// Keeps IWebSocketResource instances identified by id. + /// As defined in WebSocket.js. + /// + std::map> ResourceMap{}; - WebSocketModule(winrt::Windows::Foundation::IInspectable const &inspectableProperties); + /// + /// Generates IWebSocketResource instances, defaulting to IWebSocketResource::Make. + /// + std::function(std::string&&)> ResourceFactory; - ~WebSocketModule() noexcept override; + /// + /// Keeps a raw reference to the module object to lazily retrieve the React Instance as needed. + /// + CxxModule* Module{nullptr}; + + // Property bag high level reference. + winrt::Windows::Foundation::IInspectable InspectableProps; + }; + + #pragma region CxxModule overrides - struct SharedState { /// - /// Keeps IWebSocketResource instances identified by id. - /// As defined in WebSocket.js. + /// /// - std::map> ResourceMap{}; + std::string getName() override; /// - /// Generates IWebSocketResource instances, defaulting to IWebSocketResource::Make. + /// /// - std::function(std::string &&)> ResourceFactory; + std::map getConstants() override; /// - /// Keeps a raw reference to the module object to lazily retrieve the React Instance as needed. + /// /// - CxxModule *Module{nullptr}; - - // Property bag high level reference. - winrt::Windows::Foundation::IInspectable InspectableProps; + /// See See react-native/Libraries/WebSocket/WebSocket.js + std::vector getMethods() override; + + #pragma endregion CxxModule overrides + + void SetResourceFactory( + std::function(const std::string&)>&& resourceFactory); + + private: + /// + /// Keeps IWebSocketResource instances identified by id. + /// As defined in WebSocket.js. + /// + std::map> m_webSockets; + + /// + /// Keeps members that can be accessed threads other than this module's owner accessible. + /// + std::shared_ptr m_sharedState; + + /// + /// Exposes a subset of the module's methods. + /// + std::shared_ptr m_proxy; }; -#pragma region CxxModule overrides - - /// - /// - /// - std::string getName() override; - - /// - /// - /// - std::map getConstants() override; - - /// - /// - /// - /// See See react-native/Libraries/WebSocket/WebSocket.js - std::vector getMethods() override; - -#pragma endregion CxxModule overrides - - void SetResourceFactory( - std::function(const std::string &)> &&resourceFactory); - - private: - /// - /// Keeps IWebSocketResource instances identified by id. - /// As defined in WebSocket.js. - /// - std::map> m_webSockets; - - /// - /// Keeps members that can be accessed threads other than this module's owner accessible. - /// - std::shared_ptr m_sharedState; - - /// - /// Exposes a subset of the module's methods. - /// - std::shared_ptr m_proxy; -}; - } // namespace Microsoft::React diff --git a/vnext/Shared/Networking/IHttpResource.h b/vnext/Shared/Networking/IHttpResource.h index 7a8434e9f6f..2e999bb5755 100644 --- a/vnext/Shared/Networking/IHttpResource.h +++ b/vnext/Shared/Networking/IHttpResource.h @@ -3,12 +3,6 @@ #pragma once -// Folly -#include - -// Windows API -#include - // Standard Library #include #include @@ -20,7 +14,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; @@ -34,53 +27,13 @@ struct IHttpResource { static std::shared_ptr Make() noexcept; - static std::shared_ptr Make( - winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept; - virtual ~IHttpResource() noexcept {} - /// - /// Initiates an HTTP request. - /// - /// - /// HTTP verb to send in de request. - /// GET | POST | PUT | DELETE | OPTIONS - /// - /// - /// Server/service remote endpoint to send the request to. - /// - /// - /// Request unique identifier. - /// - /// - /// HTTP request header map. - /// - /// - /// Dynamic map containing request payload. - /// The payload may be an empty request body or one of the following: - /// "string" - UTF-8 string payload - /// "base64" - Base64-encoded data string - /// "uri" - URI data reference - /// "form" - Form-encoded data - /// - /// - /// text | binary | blob - /// - /// - /// Response body to be retrieved in several iterations. - /// - /// - /// Request timeout in miliseconds. - /// - /// - /// Allow including credentials in request. - /// virtual void SendRequest( std::string &&method, std::string &&url, - int64_t requestId, Headers &&headers, - folly::dynamic &&data, + BodyData &&bodyData, std::string &&responseType, bool useIncrementalUpdates, int64_t timeout, @@ -90,10 +43,9 @@ struct IHttpResource { virtual void ClearCookies() noexcept = 0; - virtual void SetOnRequestSuccess(std::function &&handler) noexcept = 0; + virtual void SetOnRequest(std::function &&handler) noexcept = 0; virtual void SetOnResponse(std::function &&handler) noexcept = 0; virtual void SetOnData(std::function &&handler) noexcept = 0; - virtual void SetOnData(std::function &&handler) noexcept = 0; virtual void SetOnError( std::function &&handler) noexcept = 0; }; diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index a496186d769..1911427748f 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -4,7 +4,6 @@ #include "WinRTHttpResource.h" #include -#include #include #include #include @@ -18,14 +17,10 @@ #include #include -using folly::dynamic; - using std::function; using std::scoped_lock; using std::shared_ptr; using std::string; -using std::vector; -using std::weak_ptr; using winrt::fire_and_forget; using winrt::hresult_error; @@ -50,25 +45,29 @@ namespace Microsoft::React::Networking { #pragma region WinRTHttpResource +// TODO: Check for multi-thread issues if there are multiple instances. +/*static*/ int64_t WinRTHttpResource::s_lastRequestId = 0; + WinRTHttpResource::WinRTHttpResource(IHttpClient &&client) noexcept : m_client{std::move(client)} {} -WinRTHttpResource::WinRTHttpResource() noexcept : WinRTHttpResource(winrt::Windows::Web::Http::HttpClient{}) {} +WinRTHttpResource::WinRTHttpResource() noexcept : WinRTHttpResource(winrt::Windows::Web::Http::HttpClient()) {} #pragma region IHttpResource void WinRTHttpResource::SendRequest( string &&method, string &&url, - int64_t requestId, Headers &&headers, - dynamic &&data, + BodyData &&bodyData, string &&responseType, bool useIncrementalUpdates, int64_t timeout, bool withCredentials, std::function &&callback) noexcept /*override*/ { + auto requestId = ++s_lastRequestId; + // Enforce supported args - assert(responseType == "text" || responseType == "base64" || responseType == "blob"); + assert(responseType == "text" || responseType == "base64"); if (callback) { callback(requestId); @@ -83,10 +82,10 @@ void WinRTHttpResource::SendRequest( auto concreteArgs = args.as(); concreteArgs->RequestId = requestId; concreteArgs->Headers = std::move(headers); - concreteArgs->Data = std::move(data); + concreteArgs->Body = std::move(bodyData); concreteArgs->IncrementalUpdates = useIncrementalUpdates; concreteArgs->WithCredentials = withCredentials; - concreteArgs->ResponseType = std::move(responseType); + concreteArgs->IsText = responseType == "text"; concreteArgs->Timeout = timeout; PerformSendRequest(std::move(request), args); @@ -127,8 +126,8 @@ void WinRTHttpResource::ClearCookies() noexcept /*override*/ { // NOT IMPLEMENTED } -void WinRTHttpResource::SetOnRequestSuccess(function &&handler) noexcept /*override*/ { - m_onRequestSuccess = std::move(handler); +void WinRTHttpResource::SetOnRequest(function &&handler) noexcept /*override*/ { + m_onRequest = std::move(handler); } void WinRTHttpResource::SetOnResponse(function &&handler) noexcept @@ -141,12 +140,6 @@ void WinRTHttpResource::SetOnData(function &&handler) noexcept -/*override*/ -{ - m_onDataDynamic = std::move(handler); -} - void WinRTHttpResource::SetOnError(function &&handler) noexcept /*override*/ { m_onError = std::move(handler); @@ -174,28 +167,6 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque // Ensure background thread co_await winrt::resume_background(); - // If URI handler is available, it takes over request processing. - if (auto uriHandler = self->m_uriHandler.lock()) { - auto uri = winrt::to_string(coRequest.RequestUri().ToString()); - try { - if (uriHandler->Supports(uri, coReqArgs->ResponseType)) { - auto blob = uriHandler->Fetch(uri); - if (self->m_onDataDynamic && self->m_onRequestSuccess) { - self->m_onDataDynamic(coReqArgs->RequestId, std::move(blob)); - self->m_onRequestSuccess(coReqArgs->RequestId); - } - - co_return; - } - } catch (const hresult_error &e) { - if (self->m_onError) - co_return self->m_onError(coReqArgs->RequestId, Utilities::HResultToString(e)); - } catch (const std::exception &e) { - if (self->m_onError) - co_return self->m_onError(coReqArgs->RequestId, e.what()); - } - } - HttpMediaTypeHeaderValue contentType{nullptr}; string contentEncoding; string contentLength; @@ -230,44 +201,20 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque } IHttpContent content{nullptr}; - auto &data = coReqArgs->Data; - if (!data.isNull()) { - auto bodyHandler = self->m_requestBodyHandler.lock(); - if (bodyHandler && bodyHandler->Supports(data)) { - auto contentTypeString = contentType ? winrt::to_string(contentType.ToString()) : ""; - dynamic blob; - try { - blob = bodyHandler->ToRequestBody(data, contentTypeString); - } catch (const std::invalid_argument &e) { - if (self->m_onError) { - self->m_onError(coReqArgs->RequestId, e.what()); - } - co_return; - } - auto bytes = blob["bytes"]; - auto byteVector = vector(bytes.size()); - for (auto &byte : bytes) { - byteVector.push_back(static_cast(byte.asInt())); - } - auto view = winrt::array_view{byteVector}; - auto buffer = CryptographicBuffer::CreateFromByteArray(view); - content = HttpBufferContent{std::move(buffer)}; - } else if (!data["string"].empty()) { - content = HttpStringContent{to_hstring(data["string"].asString())}; - } else if (!data["base64"].empty()) { - 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())}); - auto stream = co_await file.OpenReadAsync(); - content = HttpStreamContent{std::move(stream)}; - } else if (!data["form"].empty()) { - // #9535 - HTTP form data support - // winrt::Windows::Web::Http::HttpMultipartFormDataContent() - } else { - // Assume empty request body. - // content = HttpStringContent{L""}; - } + if (BodyData::Type::String == coReqArgs->Body.Type) { + content = HttpStringContent{to_hstring(coReqArgs->Body.Data)}; + } else if (BodyData::Type::Base64 == coReqArgs->Body.Type) { + auto buffer = CryptographicBuffer::DecodeFromBase64String(to_hstring(coReqArgs->Body.Data)); + content = HttpBufferContent{buffer}; + } else if (BodyData::Type::Uri == coReqArgs->Body.Type) { + auto file = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{to_hstring(coReqArgs->Body.Data)}); + auto stream = co_await file.OpenReadAsync(); + content = HttpStreamContent{stream}; + } else if (BodyData::Type::Form == coReqArgs->Body.Type) { + // #9535 - HTTP form data support + } else { + // BodyData::Type::Empty + // TODO: Error => unsupported?? } if (content != nullptr) { @@ -277,13 +224,12 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque } if (!contentEncoding.empty()) { if (!content.Headers().ContentEncoding().TryParseAdd(to_hstring(contentEncoding))) { - if (self->m_onError) - self->m_onError(coReqArgs->RequestId, "Failed to parse Content-Encoding"); - + if (m_onError) { + m_onError(coReqArgs->RequestId, "Failed to parse Content-Encoding"); + } co_return; } } - if (!contentLength.empty()) { const auto contentLengthHeader = _atoi64(contentLength.c_str()); content.Headers().ContentLength(contentLengthHeader); @@ -309,7 +255,7 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque auto response = sendRequestOp.GetResults(); if (response) { if (self->m_onResponse) { - auto url = to_string(response.RequestMessage().RequestUri().AbsoluteUri()); + string url = to_string(response.RequestMessage().RequestUri().AbsoluteUri()); // Gather headers for both the response content and the response itself // See Invoke-WebRequest PowerShell cmdlet or Chromium response handling @@ -332,54 +278,30 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque auto inputStream = co_await response.Content().ReadAsInputStreamAsync(); auto reader = DataReader{inputStream}; + if (coReqArgs->IsText) { + reader.UnicodeEncoding(UnicodeEncoding::Utf8); + } + // #9510 - 10mb limit on fetch co_await reader.LoadAsync(10 * 1024 * 1024); + auto length = reader.UnconsumedBufferLength(); - // Let response handler take over, if set - if (auto responseHandler = self->m_responseHandler.lock()) { - if (responseHandler->Supports(coReqArgs->ResponseType)) { - auto bytes = vector(reader.UnconsumedBufferLength()); - reader.ReadBytes(bytes); - auto blob = responseHandler->ToResponseData(std::move(bytes)); - - if (self->m_onDataDynamic && self->m_onRequestSuccess) { - self->m_onDataDynamic(coReqArgs->RequestId, std::move(blob)); - self->m_onRequestSuccess(coReqArgs->RequestId); - } + if (coReqArgs->IsText) { + std::vector data(length); + reader.ReadBytes(data); + string responseData = string(Common::Utilities::CheckedReinterpretCast(data.data()), data.size()); - co_return; + if (self->m_onData) { + self->m_onData(coReqArgs->RequestId, std::move(responseData)); } - } - - auto isText = coReqArgs->ResponseType == "text"; - if (isText) { - reader.UnicodeEncoding(UnicodeEncoding::Utf8); - } + } else { + auto buffer = reader.ReadBuffer(length); + auto data = CryptographicBuffer::EncodeToBase64String(buffer); + auto responseData = to_string(std::wstring_view(data)); - // #9510 - We currently accumulate all incoming request data in 10MB chunks. - uint32_t segmentSize = 10 * 1024 * 1024; - string responseData; - winrt::Windows::Storage::Streams::IBuffer buffer; - uint32_t length; - do { - co_await reader.LoadAsync(segmentSize); - length = reader.UnconsumedBufferLength(); - - if (isText) { - auto data = std::vector(length); - reader.ReadBytes(data); - - responseData += string(Common::Utilities::CheckedReinterpretCast(data.data()), data.size()); - } else { - buffer = reader.ReadBuffer(length); - auto data = CryptographicBuffer::EncodeToBase64String(buffer); - - responseData += to_string(std::wstring_view(data)); + if (self->m_onData) { + self->m_onData(coReqArgs->RequestId, std::move(responseData)); } - } while (length > 0); - - if (self->m_onData) { - self->m_onData(coReqArgs->RequestId, std::move(responseData)); } } else { if (self->m_onError) { @@ -401,63 +323,23 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque } self->UntrackResponse(coReqArgs->RequestId); -} // PerformSendRequest - -#pragma region IHttpModuleProxy - -void WinRTHttpResource::AddUriHandler(shared_ptr /*uriHandler*/) noexcept /*override*/ -{ - // TODO: Implement custom URI handling. -} - -void WinRTHttpResource::AddRequestBodyHandler(shared_ptr requestBodyHandler) noexcept /*override*/ -{ - m_requestBodyHandler = weak_ptr(requestBodyHandler); } -void WinRTHttpResource::AddResponseHandler(shared_ptr responseHandler) noexcept /*override*/ -{ - m_responseHandler = weak_ptr(responseHandler); -} - -#pragma endregion IHttpModuleProxy - #pragma endregion WinRTHttpResource #pragma region IHttpResource -/*static*/ shared_ptr IHttpResource::Make( - winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept { - using namespace winrt::Microsoft::ReactNative; - using winrt::Windows::Web::Http::HttpClient; - - shared_ptr result; - +/*static*/ shared_ptr IHttpResource::Make() noexcept { if (static_cast(GetRuntimeOptionInt("Http.OriginPolicy")) == OriginPolicy::None) { - result = std::make_shared(); + return std::make_shared(); } else { auto globalOrigin = GetRuntimeOptionString("Http.GlobalOrigin"); OriginPolicyHttpFilter::SetStaticOrigin(std::move(globalOrigin)); auto opFilter = winrt::make(); - auto client = HttpClient{opFilter}; + auto client = winrt::Windows::Web::Http::HttpClient{opFilter}; - result = std::make_shared(std::move(client)); + return std::make_shared(std::move(client)); } - - // Register resource as HTTP module proxy. - if (inspectableProperties) { - auto propId = ReactPropertyId>>{L"HttpModule.Proxy"}; - auto propBag = ReactPropertyBag{inspectableProperties.try_as()}; - auto moduleProxy = weak_ptr{result}; - propBag.Set(propId, std::move(moduleProxy)); - } - - return result; -} - -/*static*/ shared_ptr IHttpResource::Make() noexcept { - auto inspectableProperties = IInspectable{nullptr}; - return Make(inspectableProperties); } #pragma endregion IHttpResource diff --git a/vnext/Shared/Networking/WinRTHttpResource.h b/vnext/Shared/Networking/WinRTHttpResource.h index 38d030f8264..c9dd8b32da5 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.h +++ b/vnext/Shared/Networking/WinRTHttpResource.h @@ -5,7 +5,6 @@ #include "IHttpResource.h" -#include #include "WinRTTypes.h" // Windows API @@ -16,71 +15,66 @@ namespace Microsoft::React::Networking { -class WinRTHttpResource : public IHttpResource, - public IHttpModuleProxy, - public std::enable_shared_from_this { - winrt::Windows::Web::Http::IHttpClient m_client; - std::mutex m_mutex; - std::unordered_map m_responses; + class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_this { + static int64_t s_lastRequestId; - std::function m_onRequestSuccess; - std::function m_onResponse; - std::function m_onData; - std::function m_onDataDynamic; - std::function m_onError; + winrt::Windows::Web::Http::IHttpClient m_client; + std::mutex m_mutex; + std::unordered_map m_responses; - // Used for IHttpModuleProxy - std::weak_ptr m_uriHandler; - std::weak_ptr m_requestBodyHandler; - std::weak_ptr m_responseHandler; + std::function m_onRequestSuccess; + std::function m_onResponse; + std::function m_onData; + std::function m_onDataDynamic; + std::function m_onError; - void TrackResponse(int64_t requestId, ResponseOperation response) noexcept; + void TrackResponse(int64_t requestId, ResponseOperation response) noexcept; - void UntrackResponse(int64_t requestId) noexcept; + void UntrackResponse(int64_t requestId) noexcept; - winrt::fire_and_forget PerformSendRequest( - winrt::Windows::Web::Http::HttpRequestMessage &&request, - winrt::Windows::Foundation::IInspectable const &args) noexcept; + winrt::fire_and_forget PerformSendRequest( + winrt::Windows::Web::Http::HttpRequestMessage&& request, + winrt::Windows::Foundation::IInspectable const& args) noexcept; - public: - WinRTHttpResource() noexcept; + public: + WinRTHttpResource() noexcept; - WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient &&client) noexcept; + WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient&& client) noexcept; #pragma region IHttpResource - void SendRequest( - std::string &&method, - std::string &&url, + void SendRequest( + std::string&& method, + std::string&& url, int64_t requestId, - Headers &&headers, - folly::dynamic &&data, - std::string &&responseType, + Headers&& headers, + folly::dynamic&& data, + std::string&& responseType, bool useIncrementalUpdates, int64_t timeout, bool withCredentials, - std::function &&callback) noexcept override; - void AbortRequest(int64_t requestId) noexcept override; - void ClearCookies() noexcept override; + std::function&& callback) noexcept override; + void AbortRequest(int64_t requestId) noexcept override; + void ClearCookies() noexcept override; - 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 SetOnError(std::function - &&handler) noexcept override; + 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 SetOnError(std::function + && handler) noexcept override; #pragma endregion IHttpResource #pragma region IHttpModuleProxy - void AddUriHandler(std::shared_ptr uriHandler) noexcept override; + void AddUriHandler(std::shared_ptr uriHandler) noexcept override; - void AddRequestBodyHandler(std::shared_ptr requestBodyHandler) noexcept override; + void AddRequestBodyHandler(std::shared_ptr requestBodyHandler) noexcept override; - void AddResponseHandler(std::shared_ptr responseHandler) noexcept override; + void AddResponseHandler(std::shared_ptr responseHandler) noexcept override; #pragma endregion IHttpModuleProxy -}; + }; } // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/WinRTTypes.h b/vnext/Shared/Networking/WinRTTypes.h index d0bbafc27a0..4ef2ba4b2c7 100644 --- a/vnext/Shared/Networking/WinRTTypes.h +++ b/vnext/Shared/Networking/WinRTTypes.h @@ -5,9 +5,6 @@ #include "IHttpResource.h" -// Folly -#include - // Windows API #include @@ -16,18 +13,18 @@ namespace Microsoft::React::Networking { -struct RequestArgs : public winrt::implements { - int64_t RequestId; - IHttpResource::Headers Headers; - folly::dynamic Data; - bool IncrementalUpdates; - bool WithCredentials; - std::string ResponseType; - int64_t Timeout; -}; - -typedef winrt::Windows::Foundation:: + struct RequestArgs : public winrt::implements { + int64_t RequestId; + IHttpResource::Headers Headers; + IHttpResource::BodyData Body; + bool IncrementalUpdates; + bool WithCredentials; + bool IsText; + int64_t Timeout; + }; + + typedef winrt::Windows::Foundation:: IAsyncOperationWithProgress - ResponseOperation; + ResponseOperation; } // namespace Microsoft::React::Networking diff --git a/vnext/Shared/RuntimeOptions.cpp b/vnext/Shared/RuntimeOptions.cpp index c149db25b4b..b443eb94ad3 100644 --- a/vnext/Shared/RuntimeOptions.cpp +++ b/vnext/Shared/RuntimeOptions.cpp @@ -13,89 +13,89 @@ using std::scoped_lock; using std::string; namespace { -std::unordered_map g_runtimeOptionInts; -std::unordered_map g_runtimeOptionStrings; -mutex g_runtimeOptionsMutex; + std::unordered_map g_runtimeOptionInts; + std::unordered_map g_runtimeOptionStrings; + mutex g_runtimeOptionsMutex; -void __cdecl GetStringCallback(const char *buffer, size_t length, void *state) { - if (!buffer) - return; + void __cdecl GetStringCallback(const char* buffer, size_t length, void* state) { + if (!buffer) + return; - *static_cast(state) = static_cast(malloc(length)); - strncpy_s(*static_cast(state), length, buffer, length); -} + *static_cast(state) = static_cast(malloc(length)); + strncpy_s(*static_cast(state), length, buffer, length); + } } // namespace namespace Microsoft::React { -void __cdecl SetRuntimeOptionBool(string &&name, bool value) noexcept { - MicrosoftReactSetRuntimeOptionBool(name.c_str(), value); -} + void __cdecl SetRuntimeOptionBool(string&& name, bool value) noexcept { + MicrosoftReactSetRuntimeOptionBool(name.c_str(), value); + } -void __cdecl SetRuntimeOptionInt(string &&name, int32_t value) noexcept { - MicrosoftReactSetRuntimeOptionInt(name.c_str(), value); -} + void __cdecl SetRuntimeOptionInt(string&& name, int32_t value) noexcept { + MicrosoftReactSetRuntimeOptionInt(name.c_str(), value); + } -void __cdecl SetRuntimeOptionString(string &&name, string &&value) noexcept { - if (!value.empty()) - MicrosoftReactSetRuntimeOptionString(std::move(name).c_str(), std::move(value).c_str()); -} + void __cdecl SetRuntimeOptionString(string&& name, string&& value) noexcept { + if (!value.empty()) + MicrosoftReactSetRuntimeOptionString(std::move(name).c_str(), std::move(value).c_str()); + } -const bool __cdecl GetRuntimeOptionBool(const string &name) noexcept { - return MicrosoftReactGetRuntimeOptionBool(name.c_str()); -} + const bool __cdecl GetRuntimeOptionBool(const string& name) noexcept { + return MicrosoftReactGetRuntimeOptionBool(name.c_str()); + } -const int32_t __cdecl GetRuntimeOptionInt(const string &name) noexcept { - return MicrosoftReactGetRuntimeOptionInt(name.c_str()); -} + const int32_t __cdecl GetRuntimeOptionInt(const string& name) noexcept { + return MicrosoftReactGetRuntimeOptionInt(name.c_str()); + } -const string __cdecl GetRuntimeOptionString(const string &name) noexcept { - char *payload{nullptr}; - MicrosoftReactGetRuntimeOptionString(name.c_str(), GetStringCallback, &payload); + const string __cdecl GetRuntimeOptionString(const string& name) noexcept { + char* payload{ nullptr }; + MicrosoftReactGetRuntimeOptionString(name.c_str(), GetStringCallback, &payload); - if (!payload) - return string{}; + if (!payload) + return string{}; - return string{std::move(payload)}; -} + return string{ std::move(payload) }; + } } // namespace Microsoft::React -void __cdecl MicrosoftReactSetRuntimeOptionBool(const char *name, bool value) noexcept { +void __cdecl MicrosoftReactSetRuntimeOptionBool(const char* name, bool value) noexcept { if (!name) return; - scoped_lock lock{g_runtimeOptionsMutex}; + scoped_lock lock{ g_runtimeOptionsMutex }; if (value) g_runtimeOptionInts.insert_or_assign(name, 1); else g_runtimeOptionInts.erase(name); } -void __cdecl MicrosoftReactSetRuntimeOptionInt(const char *name, int32_t value) noexcept { +void __cdecl MicrosoftReactSetRuntimeOptionInt(const char* name, int32_t value) noexcept { if (!name) return; - scoped_lock lock{g_runtimeOptionsMutex}; + scoped_lock lock{ g_runtimeOptionsMutex }; if (value) g_runtimeOptionInts.insert_or_assign(name, value); else g_runtimeOptionInts.erase(name); } -void __cdecl MicrosoftReactSetRuntimeOptionString(const char *name, const char *value) noexcept { +void __cdecl MicrosoftReactSetRuntimeOptionString(const char* name, const char* value) noexcept { if (!name) return; - scoped_lock lock{g_runtimeOptionsMutex}; + scoped_lock lock{ g_runtimeOptionsMutex }; if (value) g_runtimeOptionStrings.insert_or_assign(name, value); else g_runtimeOptionStrings.erase(name); } -bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept { - scoped_lock lock{g_runtimeOptionsMutex}; +const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char* name) noexcept { + scoped_lock lock{ g_runtimeOptionsMutex }; auto itr = g_runtimeOptionInts.find(name); if (itr != g_runtimeOptionInts.end()) return itr->second != 0; @@ -103,8 +103,8 @@ bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept { return false; } -int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexcept { - scoped_lock lock{g_runtimeOptionsMutex}; +const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char* name) noexcept { + scoped_lock lock{ g_runtimeOptionsMutex }; auto itr = g_runtimeOptionInts.find(name); if (itr != g_runtimeOptionInts.end()) return itr->second; @@ -112,15 +112,13 @@ int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexcept { return 0; } -void __cdecl MicrosoftReactGetRuntimeOptionString( - const char *name, - MicrosoftReactGetStringCallback callBack, - void *state) { - scoped_lock lock{g_runtimeOptionsMutex}; +void MicrosoftReactGetRuntimeOptionString(const char* name, MicrosoftReactGetStringCallback callBack, void* state) { + scoped_lock lock{ g_runtimeOptionsMutex }; auto itr = g_runtimeOptionStrings.find(name); if (itr != g_runtimeOptionStrings.cend()) { callBack(itr->second.c_str(), itr->second.size() * sizeof(char) + 1 /*NULL termination*/, state); - } else { + } + else { callBack(nullptr, 0, state); } } diff --git a/vnext/Shared/RuntimeOptions.h b/vnext/Shared/RuntimeOptions.h index 8860d4a9c76..a9b4c87e7ec 100644 --- a/vnext/Shared/RuntimeOptions.h +++ b/vnext/Shared/RuntimeOptions.h @@ -3,46 +3,42 @@ #pragma once -#ifdef __cplusplus -extern "C" { -#endif - /// /// Sets a global boolean value identified by an arbitrary string. /// /// Global key -void __cdecl MicrosoftReactSetRuntimeOptionBool(const char *name, bool value) noexcept; +void __cdecl MicrosoftReactSetRuntimeOptionBool(const char* name, bool value) noexcept; /// /// Sets a global signed integer value identified by an arbitrary string. /// /// Global boolean key -void __cdecl MicrosoftReactSetRuntimeOptionInt(const char *name, int32_t value) noexcept; +void __cdecl MicrosoftReactSetRuntimeOptionInt(const char* name, int32_t value) noexcept; /// /// Sets a global signed integer value identified by an arbitrary string. /// /// Global string key -void __cdecl MicrosoftReactSetRuntimeOptionString(const char *name, const char *value) noexcept; +void __cdecl MicrosoftReactSetRuntimeOptionString(const char* name, const char* value) noexcept; /// /// Retrieves a global boolean value for the given key. /// /// Global boolean key /// Value stored for the given key, or false if the entry doesn't exist (default) -bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept; +const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char* name) noexcept; /// /// Retrieves a global boolean value for the given key. /// /// Global key /// Value stored for the given key, or 0 if the entry doesn't exist (default) -int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexcept; +const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char* name) noexcept; /// String contents. nullptr if none found /// String length. 0 if none found /// Pointer used to pass or retrieve arbitrary data -typedef void(__cdecl *MicrosoftReactGetStringCallback)(const char *buffer, size_t length, void *state); +typedef void(__cdecl* MicrosoftReactGetStringCallback)(const char* buffer, size_t length, void* state); /// /// Retrieves a global string value for the given key. @@ -50,11 +46,4 @@ typedef void(__cdecl *MicrosoftReactGetStringCallback)(const char *buffer, size_ /// Global key /// Handler used to access the obtained string /// Arbitrary data to pass on to or retrieve from callBack -void __cdecl MicrosoftReactGetRuntimeOptionString( - const char *name, - MicrosoftReactGetStringCallback callBack, - void *state); - -#ifdef __cplusplus -} -#endif +void MicrosoftReactGetRuntimeOptionString(const char* name, MicrosoftReactGetStringCallback callBack, void* state); diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index eb2d1a75375..c1ca1617ded 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -130,9 +130,6 @@ Source Files - - Source Files\Modules - Source Files\Modules @@ -148,12 +145,6 @@ Source Files\Networking - - Source Files\Modules - - - Source Files\Modules - @@ -399,15 +390,6 @@ Header Files - - Header Files\Modules - - - Header Files\Modules - - - Header Files\Modules - Header Files\Modules @@ -438,27 +420,6 @@ Header Files - - Header Files\Modules - - - Header Files\Modules - - - Header Files\Modules - - - Header Files\Modules - - - Header Files\Modules - - - Header Files\Modules - - - Header Files\Modules - From 3e2cdbb6a72b50472c9bea0fc404204f76d9d063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Fri, 20 May 2022 15:26:15 -0700 Subject: [PATCH 02/23] Allow >10MB HTTP downloads (#9957) * Allow fetching HTTP content by segments * Change files * Restore the 10MB download chunk size * Remove usage of Content-Length * Revert segment size to 10MB * Make segmentSize and length uint32_t * Reuse response content buffer --- ...-68257dfa-e3b3-4038-b170-6bc686cb3b08.json | 7 ++++ vnext/Shared/Networking/WinRTHttpResource.cpp | 40 ++++++++++--------- 2 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 change/react-native-windows-68257dfa-e3b3-4038-b170-6bc686cb3b08.json diff --git a/change/react-native-windows-68257dfa-e3b3-4038-b170-6bc686cb3b08.json b/change/react-native-windows-68257dfa-e3b3-4038-b170-6bc686cb3b08.json new file mode 100644 index 00000000000..cda201f8577 --- /dev/null +++ b/change/react-native-windows-68257dfa-e3b3-4038-b170-6bc686cb3b08.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Allow fetching HTTP content by segments", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index 1911427748f..aa3ed5e853e 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -282,26 +282,30 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque reader.UnicodeEncoding(UnicodeEncoding::Utf8); } - // #9510 - 10mb limit on fetch - co_await reader.LoadAsync(10 * 1024 * 1024); - auto length = reader.UnconsumedBufferLength(); - - if (coReqArgs->IsText) { - std::vector data(length); - reader.ReadBytes(data); - string responseData = string(Common::Utilities::CheckedReinterpretCast(data.data()), data.size()); - - if (self->m_onData) { - self->m_onData(coReqArgs->RequestId, std::move(responseData)); + // #9510 - We currently accumulate all incoming request data in 10MB chunks. + uint32_t segmentSize = 10 * 1024 * 1024; + string responseData; + winrt::Windows::Storage::Streams::IBuffer buffer; + uint32_t length; + do { + co_await reader.LoadAsync(segmentSize); + length = reader.UnconsumedBufferLength(); + + if (coReqArgs->IsText) { + auto data = std::vector(length); + reader.ReadBytes(data); + + responseData += string(Common::Utilities::CheckedReinterpretCast(data.data()), data.size()); + } else { + buffer = reader.ReadBuffer(length); + auto data = CryptographicBuffer::EncodeToBase64String(buffer); + + responseData += to_string(std::wstring_view(data)); } - } else { - auto buffer = reader.ReadBuffer(length); - auto data = CryptographicBuffer::EncodeToBase64String(buffer); - auto responseData = to_string(std::wstring_view(data)); + } while (length > 0); - if (self->m_onData) { - self->m_onData(coReqArgs->RequestId, std::move(responseData)); - } + if (self->m_onData) { + self->m_onData(coReqArgs->RequestId, std::move(responseData)); } } else { if (self->m_onError) { From 26d9ecf86cf10437b7684e5e5c5c5bfd4881303e Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 15:51:59 -0700 Subject: [PATCH 03/23] Remove change files --- ...ative-windows-68257dfa-e3b3-4038-b170-6bc686cb3b08.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 change/react-native-windows-68257dfa-e3b3-4038-b170-6bc686cb3b08.json diff --git a/change/react-native-windows-68257dfa-e3b3-4038-b170-6bc686cb3b08.json b/change/react-native-windows-68257dfa-e3b3-4038-b170-6bc686cb3b08.json deleted file mode 100644 index cda201f8577..00000000000 --- a/change/react-native-windows-68257dfa-e3b3-4038-b170-6bc686cb3b08.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "prerelease", - "comment": "Allow fetching HTTP content by segments", - "packageName": "react-native-windows", - "email": "julio.rocha@microsoft.com", - "dependentChangeType": "patch" -} From ae782ae33b7876f80e398e91e29bc53c457a96d6 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 15:57:10 -0700 Subject: [PATCH 04/23] Implement Blob module (#9352) * Added BlobModule and IWSModuleContHandler headers * Implement Blob module * Avoid raw self pointer in BlobModule * Implement WebSocketModule msg processing * clang format * Don't return until websocketMessage event is sent * Define CreateBlobModule() * Add DEF exports * Add Blob JS tests * Add Blob JS tests * Change files * yarn lint * Add overrides * Register BlobModule in DesktopTestRunner * Keep ignoring WebSocketBlob test by default * Add BlobModule to default modules list * Allow 'blob' responseType in HTTP module * Ensure React Instance can be accessed when using older versions of react-native * Emit error message on createFromParts failure * Remove redundant extra modules in Desktop integration tests * Declare IWebSocketModuleProxy * Remove Blob and WS module factories from DLL boundary * Implement IWebSocketModuleProxy * clang format * Update packages.lock * Use winrt::array_view directly in ResolveMessage * Define InstanceImpl::m_transitionalModuleProperties * Include CreateModules.h in projects accessing MSRN.Cxx * Define WinRT class WebSocketModuleContentHandler - Have BlobModule constructor register the content handler in transitive property bag CxxNativeModule/WebSocketModuleContentHandler * Have WebSocketModule use IInspectable as props arg * Use property bag instead of global singletons for blob helpers * Store blob helpers in prop bag as weak_ptr * Replace remaining lock_guard in BlobModule * Define IUriHandler, IReqBodyHandler, IRespHandler. * IHttpResource::SendRequest - add folly::dynamic data arg * Add data arg to test SendRequest calls * First implementation for BlobModuleUriHandler * Remove WebSocketModuleContentHandler WinRT class * Implement IBlobPersistor, MemoryBlobPersistor * clang format * Update yarn.lock * Update RctRootVieTagGen location * Implement addNetworkingHandler * Fix createFromParts buffer persistence * Drop WebSocketModule s_sharedState in favor of property bag * Disable back WebSocketBlob test * Rename iProperties to inspectableProperties * Pass ReactContext properties to CreateHttpModule in InstanceWin * Remove WebSocketModule constructor from x86 DLL boundary * yarn lint * Update packages.lock * Make transitional property bag non-member * Use blobURIScheme wherever possible * Pass request content as folly::dynaic. - Pass request ID directly from JavaScript layer. * Use constexpr for folly indexes * Implement GetMimeTypeFromUri * Finish BlobModule handler implementations. * Remove unused includes * Ensure HttpModule::m_resource is set * clang format * clang format * Allow blob responseType * Use winrt::to_hstring instead of Utf8ToUtf16 * Pass inspectableProperties down to WinRTHttpResource * Implement IHttpModuleProxy via WinRTHttpResource * Consume URI handler - IHttpResource - Rename SetOnRequest to SetOnRequestSuccess - Declare SetOnBlobData to pass complex (non-string) response data * Consume IRequestBodyHandler * Consume IResponseHandler * Ensure properties exist in bag before using value * Update packages lock * Add missing call to Modules::SendEvent * Fix Shared filters * Rename SetOnBlobData to SetOnData (different args) * Correctly retrieve blob slices * Correctly retrieve blob slices * Clang format * Update project filters * Drop BlobModuleUriHandler * Continue handling requests when not blob-supported * Add BlobTest * Update packages.lock.json * Define FileReaderModule * Implement FileReaderModule * Complete BlobTest * Make IBlobPersistor::ResolveMessage throw std::invalid_argument * Fail on Content-Encoding parsing even if no error handler * Remove MIME mappings. Currently unused * MemoryBlobPersistor::ResolveMessage throw on out of bounds * lint * Enable BlobTest by default * Disable Blob test in CI (may hang) --- vnext/Desktop.DLL/react-native-win32.x86.def | 1 - .../DesktopTestRunner.cpp | 3 - .../HttpOriginPolicyIntegrationTest.cpp | 10 +- .../HttpResourceIntegrationTests.cpp | 23 +- ...Microsoft.ReactNative.Cxx.vcxitems.filters | 1 + .../packages.lock.json | 49 +- .../packages.lock.json | 59 +- vnext/Shared/Modules/HttpModule.cpp | 1 + vnext/Shared/Modules/WebSocketModule.cpp | 18 +- vnext/Shared/Modules/WebSocketModule.h | 115 +- vnext/Shared/Networking/IHttpResource.h | 52 +- vnext/Shared/Networking/WinRTHttpResource.cpp | 186 ++- vnext/Shared/Networking/WinRTHttpResource.h | 12 +- vnext/Shared/Networking/WinRTTypes.h | 7 +- vnext/Shared/OInstance.cpp | 1030 +++++++++-------- vnext/Shared/OInstance.h | 167 ++- vnext/Shared/Shared.vcxitems.filters | 40 + vnext/overrides.json | 6 +- vnext/src/IntegrationTests/BlobTest.js | 2 +- .../websocket_integration_test_server_blob.js | 1 + 20 files changed, 1061 insertions(+), 722 deletions(-) diff --git a/vnext/Desktop.DLL/react-native-win32.x86.def b/vnext/Desktop.DLL/react-native-win32.x86.def index 2c4eed9cda3..891cb536520 100644 --- a/vnext/Desktop.DLL/react-native-win32.x86.def +++ b/vnext/Desktop.DLL/react-native-win32.x86.def @@ -56,7 +56,6 @@ EXPORTS ?GetRuntimeOptionString@React@Microsoft@@YA?BV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@ABV34@@Z ?makeChakraRuntime@JSI@Microsoft@@YG?AV?$unique_ptr@VRuntime@jsi@facebook@@U?$default_delete@VRuntime@jsi@facebook@@@std@@@std@@$$QAUChakraRuntimeArgs@12@@Z ?CreateTimingModule@react@facebook@@YG?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@ABV?$shared_ptr@VMessageQueueThread@react@facebook@@@4@@Z -??0WebSocketModule@React@Microsoft@@QAE@XZ ?Make@IHttpResource@Networking@React@Microsoft@@SG?AV?$shared_ptr@UIHttpResource@Networking@React@Microsoft@@@std@@XZ ??0NetworkingModule@React@Microsoft@@QAE@XZ ?MakeJSQueueThread@ReactNative@Microsoft@@YG?AV?$shared_ptr@VMessageQueueThread@react@facebook@@@std@@XZ diff --git a/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp b/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp index db877e5b9eb..b1ad4b1540b 100644 --- a/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp +++ b/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp @@ -3,9 +3,6 @@ #include -#include -#include -#include #include #include #include "ChakraRuntimeHolder.h" diff --git a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp index ce9fb266d61..640383f71e6 100644 --- a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp +++ b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp @@ -142,8 +142,9 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) resource->SendRequest( string{http::to_string(clientArgs.Method).data()}, string{server1Args.Url}, + 0, /*requestId*/ std::move(clientArgs.RequestHeaders), - { IHttpResource::BodyData::Type::String, "REQUEST_CONTENT" }, + {}, /*data*/ "text", false, /*useIncrementalUpdates*/ 1000, /*timeout*/ @@ -195,8 +196,9 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) resource->SendRequest( string{http::to_string(clientArgs.Method).data()}, string{serverArgs.Url}, + 0, /*requestId*/ std::move(clientArgs.RequestHeaders), - { IHttpResource::BodyData::Type::String, "REQUEST_CONTENT" }, + {}, /*data*/ "text", false, /*useIncrementalUpdates*/ 1000, /*timeout*/ @@ -290,10 +292,12 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) resource->SendRequest( "TRACE", url, + 0, /*requestId*/ { {"ValidHeader", "AnyValue"} }, - {} /*bodyData*/, + {}, /*data*/ + //{} /*bodyData*/, "text", false /*useIncrementalUpdates*/, 1000 /*timeout*/, diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index d0778b9bb14..94a50f65112 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -73,8 +73,9 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "GET", std::move(url), - {} /*header*/, - {} /*bodyData*/, + 0, /*requestId*/ + {}, /*header*/ + {}, /*data*/ "text", false, 1000 /*timeout*/, @@ -128,13 +129,14 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "GET", std::move(url), + 0, /*requestId*/ { {"Content-Type", "application/json"}, {"Content-Encoding", "ASCII"}, {"name3", "value3"}, {"name4", "value4"}, }, - {} /*bodyData*/, + {}, /*data*/ "text", false, 1000 /*timeout*/, @@ -238,8 +240,9 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "OPTIONS", string{url}, - {} /*headers*/, - {} /*bodyData*/, + 0, /*requestId*/ + {}, /*headers*/ + {}, /*data*/ "text", false, 1000 /*timeout*/, @@ -248,8 +251,9 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "GET", std::move(url), - {} /*headers*/, - {} /*bodyData*/, + 0, /*requestId*/ + {}, /*headers*/ + {}, /*data*/ "text", false, 1000 /*timeout*/, @@ -330,8 +334,9 @@ TEST_CLASS (HttpResourceIntegrationTest) { resource->SendRequest( "GET", std::move(url), - {} /*headers*/, - {} /*bodyData*/, + 0, /*requestId*/ + {}, /*headers*/ + {}, /*data*/ "text", false, /*useIncrementalUpdates*/ 1000 /*timeout*/, diff --git a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters index a6085917d3f..a4c7313ec27 100644 --- a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters +++ b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters @@ -30,6 +30,7 @@ JSI + diff --git a/vnext/Microsoft.ReactNative.Managed.UnitTests/packages.lock.json b/vnext/Microsoft.ReactNative.Managed.UnitTests/packages.lock.json index 265cfe2c208..dec34f0ba84 100644 --- a/vnext/Microsoft.ReactNative.Managed.UnitTests/packages.lock.json +++ b/vnext/Microsoft.ReactNative.Managed.UnitTests/packages.lock.json @@ -75,10 +75,25 @@ "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, + "Microsoft.UI.Xaml": { + "type": "Transitive", + "resolved": "2.7.0", + "contentHash": "dB4im13tfmMgL/V3Ei+3kD2rUF+/lTxAmR4gjJ45l577eljHfdo/KUrxpq/3I1Vp6e5GCDG1evDaEGuDxypLMg==" + }, + "Microsoft.Windows.CppWinRT": { + "type": "Transitive", + "resolved": "2.0.211028.7", + "contentHash": "JBGI0c3WLoU6aYJRy9Qo0MLDQfObEp+d4nrhR95iyzf7+HOgjRunHDp/6eGFREd7xq3OI1mll9ecJrMfzBvlyg==" + }, + "Microsoft.Windows.SDK.BuildTools": { + "type": "Transitive", + "resolved": "10.0.22000.194", + "contentHash": "4L0P3zqut466SIqT3VBeLTNUQTxCBDOrTRymRuROCRJKazcK7ibLz9yAO1nKWRt50ttCj39oAa2Iuz9ZTDmLlg==" + }, "NETStandard.Library": { "type": "Transitive", "resolved": "2.0.3", - "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "contentHash": "548M6mnBSJWxsIlkQHfbzoYxpiYFXZZSL00p4GHYv8PkiqFBnnT68mW5mGEsA/ch9fDO9GkPgkFQpWiXZN7mAQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } @@ -272,12 +287,42 @@ "microsoft.reactnative": { "type": "Project" }, + "fmt": { + "type": "Project" + }, + "folly": { + "type": "Project", + "dependencies": { + "boost": "1.76.0", + "fmt": "1.0.0" + } + }, + "microsoft.reactnative": { + "type": "Project", + "dependencies": { + "Common": "1.0.0", + "Folly": "1.0.0", + "Microsoft.UI.Xaml": "2.7.0", + "Microsoft.Windows.CppWinRT": "2.0.211028.7", + "Microsoft.Windows.SDK.BuildTools": "10.0.22000.194", + "ReactCommon": "1.0.0", + "ReactNative.Hermes.Windows": "0.11.0-ms.6", + "boost": "1.76.0" + } + }, "microsoft.reactnative.managed": { "type": "Project", "dependencies": { "Microsoft.NETCore.UniversalWindowsPlatform": "6.2.9", "Microsoft.ReactNative": "1.0.0" } + }, + "reactcommon": { + "type": "Project", + "dependencies": { + "Folly": "1.0.0", + "boost": "1.76.0" + } } }, "UAP,Version=v10.0.16299/win10-arm": { @@ -1761,4 +1806,4 @@ } } } -} \ No newline at end of file +} diff --git a/vnext/Microsoft.ReactNative.Managed/packages.lock.json b/vnext/Microsoft.ReactNative.Managed/packages.lock.json index 04e54db679e..decd73cbdce 100644 --- a/vnext/Microsoft.ReactNative.Managed/packages.lock.json +++ b/vnext/Microsoft.ReactNative.Managed/packages.lock.json @@ -24,6 +24,11 @@ "Microsoft.SourceLink.Common": "1.0.0" } }, + "boost": { + "type": "Transitive", + "resolved": "1.76.0", + "contentHash": "p+w3YvNdXL8Cu9Fzrmexssu0tZbWxuf6ywsQqHjDlKFE5ojXHof1HIyMC3zDLfLnh80dIeFcEUAuR2Asg/XHRA==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "1.0.0", @@ -60,14 +65,34 @@ "resolved": "1.0.0", "contentHash": "G8DuQY8/DK5NN+3jm5wcMcd9QYD90UV7MiLmdljSJixi3U/vNaeBKmmXUqI4DJCOeWizIUEh4ALhSt58mR+5eg==" }, + "Microsoft.UI.Xaml": { + "type": "Transitive", + "resolved": "2.7.0", + "contentHash": "dB4im13tfmMgL/V3Ei+3kD2rUF+/lTxAmR4gjJ45l577eljHfdo/KUrxpq/3I1Vp6e5GCDG1evDaEGuDxypLMg==" + }, + "Microsoft.Windows.CppWinRT": { + "type": "Transitive", + "resolved": "2.0.211028.7", + "contentHash": "JBGI0c3WLoU6aYJRy9Qo0MLDQfObEp+d4nrhR95iyzf7+HOgjRunHDp/6eGFREd7xq3OI1mll9ecJrMfzBvlyg==" + }, + "Microsoft.Windows.SDK.BuildTools": { + "type": "Transitive", + "resolved": "10.0.22000.194", + "contentHash": "4L0P3zqut466SIqT3VBeLTNUQTxCBDOrTRymRuROCRJKazcK7ibLz9yAO1nKWRt50ttCj39oAa2Iuz9ZTDmLlg==" + }, "NETStandard.Library": { "type": "Transitive", "resolved": "2.0.3", - "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "contentHash": "548M6mnBSJWxsIlkQHfbzoYxpiYFXZZSL00p4GHYv8PkiqFBnnT68mW5mGEsA/ch9fDO9GkPgkFQpWiXZN7mAQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, + "ReactNative.Hermes.Windows": { + "type": "Transitive", + "resolved": "0.11.0-ms.6", + "contentHash": "WAVLsSZBV4p/3hNC3W67su7xu3f/ZMSKxu0ON7g2GaKRbkJmH0Qyif1IlzcJwtvR48kuOdfgPu7Bgtz3AY+gqg==" + }, "runtime.win10-arm.Microsoft.Net.Native.Compiler": { "type": "Transitive", "resolved": "2.2.7-rel-27913-00", @@ -135,8 +160,38 @@ "resolved": "2.2.9", "contentHash": "qF6RRZKaflI+LR1YODNyWYjq5YoX8IJ2wx5y8O+AW2xO+1t/Q6Mm+jQ38zJbWnmXbrcOqUYofn7Y3/KC6lTLBQ==" }, - "microsoft.reactnative": { + "common": { "type": "Project" + }, + "fmt": { + "type": "Project" + }, + "folly": { + "type": "Project", + "dependencies": { + "boost": "1.76.0", + "fmt": "1.0.0" + } + }, + "microsoft.reactnative": { + "type": "Project", + "dependencies": { + "Common": "1.0.0", + "Folly": "1.0.0", + "Microsoft.UI.Xaml": "2.7.0", + "Microsoft.Windows.CppWinRT": "2.0.211028.7", + "Microsoft.Windows.SDK.BuildTools": "10.0.22000.194", + "ReactCommon": "1.0.0", + "ReactNative.Hermes.Windows": "0.11.0-ms.6", + "boost": "1.76.0" + } + }, + "reactcommon": { + "type": "Project", + "dependencies": { + "Folly": "1.0.0", + "boost": "1.76.0" + } } }, "UAP,Version=v10.0.16299/win10-arm": { diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index aa18300a97f..7132f072b16 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -25,6 +25,7 @@ using winrt::Windows::Foundation::IInspectable; namespace { + using Microsoft::React::Modules::SendEvent; using Microsoft::React::Networking::IHttpResource; constexpr char moduleName[] = "Networking"; diff --git a/vnext/Shared/Modules/WebSocketModule.cpp b/vnext/Shared/Modules/WebSocketModule.cpp index bec9290dacb..7a53b4e5789 100644 --- a/vnext/Shared/Modules/WebSocketModule.cpp +++ b/vnext/Shared/Modules/WebSocketModule.cpp @@ -37,7 +37,9 @@ using winrt::Windows::Foundation::IInspectable; using winrt::Windows::Security::Cryptography::CryptographicBuffer; namespace { + using Microsoft::React::IWebSocketModuleProxy; using Microsoft::React::WebSocketModule; + using Microsoft::React::Modules::SendEvent; using Microsoft::React::Networking::IWebSocketResource; constexpr char moduleName[] = "WebSocketModule"; @@ -208,16 +210,16 @@ namespace Microsoft::React { } } - IWebSocketResource::Options options; - dynamic optionsDynamic = jsArgAsDynamic(args, 2); - if (!optionsDynamic.empty() && optionsDynamic.count("headers") != 0) + IWebSocketResource::Options options; + dynamic optionsDynamic = jsArgAsDynamic(args, 2); + if (!optionsDynamic.empty() && optionsDynamic.count("headers") != 0) + { + const auto& headersDynamic = optionsDynamic["headers"]; + for (const auto& header : headersDynamic.items()) { - const auto& headersDynamic = optionsDynamic["headers"]; - for (const auto& header : headersDynamic.items()) - { - options.emplace(winrt::to_hstring(header.first.getString()), header.second.getString()); - } + options.emplace(winrt::to_hstring(header.first.getString()), header.second.getString()); } + } weak_ptr weakWs = GetOrCreateWebSocket(jsArgAsInt(args, 3), jsArgAsString(args, 0), weakState); if (auto sharedWs = weakWs.lock()) diff --git a/vnext/Shared/Modules/WebSocketModule.h b/vnext/Shared/Modules/WebSocketModule.h index ea1b7e56bc6..d67878f838c 100644 --- a/vnext/Shared/Modules/WebSocketModule.h +++ b/vnext/Shared/Modules/WebSocketModule.h @@ -3,11 +3,15 @@ #pragma once +#include #include // React Native #include +// Windows API +#include + namespace Microsoft::React { class WebSocketModuleProxy final : public IWebSocketModuleProxy { @@ -32,70 +36,73 @@ namespace Microsoft::React { public: enum MethodId { Connect = 0, Close = 1, Send = 2, SendBinary = 3, Ping = 4, SIZE = 5 }; - WebSocketModule(winrt::Windows::Foundation::IInspectable const& inspectableProperties); + WebSocketModule(winrt::Windows::Foundation::IInspectable const& inspectablePropertieswinrt::Windows::Foundation::IInspectable const& inspectableProperties); + + ~WebSocketModule() noexcept override; + + struct SharedState { + /// + /// Keeps IWebSocketResource instances identified by id. + /// As defined in WebSocket.js. + /// + std::map> ResourceMap{}; + + /// + /// Generates IWebSocketResource instances, defaulting to IWebSocketResource::Make. + /// + std::function(std::string&&)> ResourceFactory; + + /// + /// Keeps a raw reference to the module object to lazily retrieve the React Instance as needed. + /// + CxxModule* Module{nullptr}; + + // Property bag high level reference. + winrt::Windows::Foundation::IInspectable InspectableProps; + + // Property bag high level reference. + winrt::Windows::Foundation::IInspectable InspectableProps; + }; - ~WebSocketModule() noexcept override; + #pragma region CxxModule overrides - struct SharedState { /// - /// Keeps IWebSocketResource instances identified by id. - /// As defined in WebSocket.js. + /// /// - std::map> ResourceMap{}; + std::string getName() override; /// - /// Generates IWebSocketResource instances, defaulting to IWebSocketResource::Make. + /// /// - std::function(std::string&&)> ResourceFactory; + std::map getConstants() override; /// - /// Keeps a raw reference to the module object to lazily retrieve the React Instance as needed. + /// /// - CxxModule* Module{nullptr}; - - // Property bag high level reference. - winrt::Windows::Foundation::IInspectable InspectableProps; - }; - - #pragma region CxxModule overrides - - /// - /// - /// - std::string getName() override; - - /// - /// - /// - std::map getConstants() override; - - /// - /// - /// - /// See See react-native/Libraries/WebSocket/WebSocket.js - std::vector getMethods() override; - - #pragma endregion CxxModule overrides - - void SetResourceFactory( - std::function(const std::string&)>&& resourceFactory); - - private: - /// - /// Keeps IWebSocketResource instances identified by id. - /// As defined in WebSocket.js. - /// - std::map> m_webSockets; - - /// - /// Keeps members that can be accessed threads other than this module's owner accessible. - /// - std::shared_ptr m_sharedState; - - /// - /// Exposes a subset of the module's methods. - /// - std::shared_ptr m_proxy; + /// See See react-native/Libraries/WebSocket/WebSocket.js + std::vector getMethods() override; + + #pragma endregion CxxModule overrides + + void SetResourceFactory( + std::function(const std::string&)>&& resourceFactory); + + private: + /// + /// Keeps IWebSocketResource instances identified by id. + /// As defined in WebSocket.js. + /// + std::map> m_webSockets; + + /// + /// Keeps members that can be accessed threads other than this module's owner accessible. + /// + std::shared_ptr m_sharedState; + + /// + /// Exposes a subset of the module's methods. + /// + std::shared_ptr m_proxy; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Networking/IHttpResource.h b/vnext/Shared/Networking/IHttpResource.h index 2e999bb5755..7a8434e9f6f 100644 --- a/vnext/Shared/Networking/IHttpResource.h +++ b/vnext/Shared/Networking/IHttpResource.h @@ -3,6 +3,12 @@ #pragma once +// Folly +#include + +// Windows API +#include + // Standard Library #include #include @@ -14,6 +20,7 @@ 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; @@ -27,13 +34,53 @@ struct IHttpResource { static std::shared_ptr Make() noexcept; + static std::shared_ptr Make( + winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept; + virtual ~IHttpResource() noexcept {} + /// + /// Initiates an HTTP request. + /// + /// + /// HTTP verb to send in de request. + /// GET | POST | PUT | DELETE | OPTIONS + /// + /// + /// Server/service remote endpoint to send the request to. + /// + /// + /// Request unique identifier. + /// + /// + /// HTTP request header map. + /// + /// + /// Dynamic map containing request payload. + /// The payload may be an empty request body or one of the following: + /// "string" - UTF-8 string payload + /// "base64" - Base64-encoded data string + /// "uri" - URI data reference + /// "form" - Form-encoded data + /// + /// + /// text | binary | blob + /// + /// + /// Response body to be retrieved in several iterations. + /// + /// + /// Request timeout in miliseconds. + /// + /// + /// Allow including credentials in request. + /// virtual void SendRequest( std::string &&method, std::string &&url, + int64_t requestId, Headers &&headers, - BodyData &&bodyData, + folly::dynamic &&data, std::string &&responseType, bool useIncrementalUpdates, int64_t timeout, @@ -43,9 +90,10 @@ struct IHttpResource { virtual void ClearCookies() noexcept = 0; - virtual void SetOnRequest(std::function &&handler) noexcept = 0; + virtual void SetOnRequestSuccess(std::function &&handler) noexcept = 0; virtual void SetOnResponse(std::function &&handler) noexcept = 0; virtual void SetOnData(std::function &&handler) noexcept = 0; + virtual void SetOnData(std::function &&handler) noexcept = 0; virtual void SetOnError( std::function &&handler) noexcept = 0; }; diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index aa3ed5e853e..388d75e8c89 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -4,6 +4,7 @@ #include "WinRTHttpResource.h" #include +#include #include #include #include @@ -17,10 +18,14 @@ #include #include +using folly::dynamic; + using std::function; using std::scoped_lock; using std::shared_ptr; using std::string; +using std::vector; +using std::weak_ptr; using winrt::fire_and_forget; using winrt::hresult_error; @@ -45,29 +50,25 @@ namespace Microsoft::React::Networking { #pragma region WinRTHttpResource -// TODO: Check for multi-thread issues if there are multiple instances. -/*static*/ int64_t WinRTHttpResource::s_lastRequestId = 0; - WinRTHttpResource::WinRTHttpResource(IHttpClient &&client) noexcept : m_client{std::move(client)} {} -WinRTHttpResource::WinRTHttpResource() noexcept : WinRTHttpResource(winrt::Windows::Web::Http::HttpClient()) {} +WinRTHttpResource::WinRTHttpResource() noexcept : WinRTHttpResource(winrt::Windows::Web::Http::HttpClient{}) {} #pragma region IHttpResource void WinRTHttpResource::SendRequest( string &&method, string &&url, + int64_t requestId, Headers &&headers, - BodyData &&bodyData, + dynamic &&data, string &&responseType, bool useIncrementalUpdates, int64_t timeout, bool withCredentials, std::function &&callback) noexcept /*override*/ { - auto requestId = ++s_lastRequestId; - // Enforce supported args - assert(responseType == "text" || responseType == "base64"); + assert(responseType == "text" || responseType == "base64" | responseType == "blob"); if (callback) { callback(requestId); @@ -82,10 +83,10 @@ void WinRTHttpResource::SendRequest( auto concreteArgs = args.as(); concreteArgs->RequestId = requestId; concreteArgs->Headers = std::move(headers); - concreteArgs->Body = std::move(bodyData); + concreteArgs->Data = std::move(data); concreteArgs->IncrementalUpdates = useIncrementalUpdates; concreteArgs->WithCredentials = withCredentials; - concreteArgs->IsText = responseType == "text"; + concreteArgs->ResponseType = std::move(responseType); concreteArgs->Timeout = timeout; PerformSendRequest(std::move(request), args); @@ -126,8 +127,8 @@ void WinRTHttpResource::ClearCookies() noexcept /*override*/ { // NOT IMPLEMENTED } -void WinRTHttpResource::SetOnRequest(function &&handler) noexcept /*override*/ { - m_onRequest = std::move(handler); +void WinRTHttpResource::SetOnRequestSuccess(function &&handler) noexcept /*override*/ { + m_onRequestSuccess = std::move(handler); } void WinRTHttpResource::SetOnResponse(function &&handler) noexcept @@ -140,6 +141,12 @@ void WinRTHttpResource::SetOnData(function &&handler) noexcept +/*override*/ +{ + m_onDataDynamic = std::move(handler); +} + void WinRTHttpResource::SetOnError(function &&handler) noexcept /*override*/ { m_onError = std::move(handler); @@ -167,6 +174,28 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque // Ensure background thread co_await winrt::resume_background(); + // If URI handler is available, it takes over request processing. + if (auto uriHandler = self->m_uriHandler.lock()) { + auto uri = winrt::to_string(coRequest.RequestUri().ToString()); + try { + if (uriHandler->Supports(uri, coReqArgs->ResponseType)) { + auto blob = uriHandler->Fetch(uri); + if (self->m_onDataDynamic && self->m_onRequestSuccess) { + self->m_onDataDynamic(coReqArgs->RequestId, std::move(blob)); + self->m_onRequestSuccess(coReqArgs->RequestId); + } + + co_return; + } + } catch (const hresult_error &e) { + if (self->m_onError) + co_return self->m_onError(coReqArgs->RequestId, Utilities::HResultToString(e)); + } catch (const std::exception &e) { + if (self->m_onError) + co_return self->m_onError(coReqArgs->RequestId, e.what()); + } + } + HttpMediaTypeHeaderValue contentType{nullptr}; string contentEncoding; string contentLength; @@ -201,20 +230,44 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque } IHttpContent content{nullptr}; - if (BodyData::Type::String == coReqArgs->Body.Type) { - content = HttpStringContent{to_hstring(coReqArgs->Body.Data)}; - } else if (BodyData::Type::Base64 == coReqArgs->Body.Type) { - auto buffer = CryptographicBuffer::DecodeFromBase64String(to_hstring(coReqArgs->Body.Data)); - content = HttpBufferContent{buffer}; - } else if (BodyData::Type::Uri == coReqArgs->Body.Type) { - auto file = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{to_hstring(coReqArgs->Body.Data)}); - auto stream = co_await file.OpenReadAsync(); - content = HttpStreamContent{stream}; - } else if (BodyData::Type::Form == coReqArgs->Body.Type) { - // #9535 - HTTP form data support - } else { - // BodyData::Type::Empty - // TODO: Error => unsupported?? + auto &data = coReqArgs->Data; + if (!data.isNull()) { + auto bodyHandler = self->m_requestBodyHandler.lock(); + if (bodyHandler && bodyHandler->Supports(data)) { + auto contentTypeString = contentType ? winrt::to_string(contentType.ToString()) : ""; + dynamic blob; + try { + blob = bodyHandler->ToRequestBody(data, contentTypeString); + } catch (const std::invalid_argument &e) { + if (self->m_onError) { + self->m_onError(coReqArgs->RequestId, e.what()); + } + co_return; + } + auto bytes = blob["bytes"]; + auto byteVector = vector(bytes.size()); + for (auto &byte : bytes) { + byteVector.push_back(static_cast(byte.asInt())); + } + auto view = winrt::array_view{byteVector}; + auto buffer = CryptographicBuffer::CreateFromByteArray(view); + content = HttpBufferContent{std::move(buffer)}; + } else if (!data["string"].empty()) { + content = HttpStringContent{to_hstring(data["string"].asString())}; + } else if (!data["base64"].empty()) { + 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())}); + auto stream = co_await file.OpenReadAsync(); + content = HttpStreamContent{std::move(stream)}; + } else if (!data["form"].empty()) { + // #9535 - HTTP form data support + // winrt::Windows::Web::Http::HttpMultipartFormDataContent() + } else { + // Assume empty request body. + // content = HttpStringContent{L""}; + } } if (content != nullptr) { @@ -224,12 +277,13 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque } if (!contentEncoding.empty()) { if (!content.Headers().ContentEncoding().TryParseAdd(to_hstring(contentEncoding))) { - if (m_onError) { - m_onError(coReqArgs->RequestId, "Failed to parse Content-Encoding"); - } + if (self->m_onError) + self->m_onError(coReqArgs->RequestId, "Failed to parse Content-Encoding"); + co_return; } } + if (!contentLength.empty()) { const auto contentLengthHeader = _atoi64(contentLength.c_str()); content.Headers().ContentLength(contentLengthHeader); @@ -255,7 +309,7 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque auto response = sendRequestOp.GetResults(); if (response) { if (self->m_onResponse) { - string url = to_string(response.RequestMessage().RequestUri().AbsoluteUri()); + auto url = to_string(response.RequestMessage().RequestUri().AbsoluteUri()); // Gather headers for both the response content and the response itself // See Invoke-WebRequest PowerShell cmdlet or Chromium response handling @@ -278,7 +332,27 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque auto inputStream = co_await response.Content().ReadAsInputStreamAsync(); auto reader = DataReader{inputStream}; - if (coReqArgs->IsText) { + // #9510 - 10mb limit on fetch + co_await reader.LoadAsync(10 * 1024 * 1024); + + // Let response handler take over, if set + if (auto responseHandler = self->m_responseHandler.lock()) { + if (responseHandler->Supports(coReqArgs->ResponseType)) { + auto bytes = vector(reader.UnconsumedBufferLength()); + reader.ReadBytes(bytes); + auto blob = responseHandler->ToResponseData(std::move(bytes)); + + if (self->m_onDataDynamic && self->m_onRequestSuccess) { + self->m_onDataDynamic(coReqArgs->RequestId, std::move(blob)); + self->m_onRequestSuccess(coReqArgs->RequestId); + } + + co_return; + } + } + + auto isText = coReqArgs->ResponseType == "text"; + if (isText) { reader.UnicodeEncoding(UnicodeEncoding::Utf8); } @@ -291,7 +365,7 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque co_await reader.LoadAsync(segmentSize); length = reader.UnconsumedBufferLength(); - if (coReqArgs->IsText) { + if (isText) { auto data = std::vector(length); reader.ReadBytes(data); @@ -327,23 +401,63 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque } self->UntrackResponse(coReqArgs->RequestId); +} // PerformSendRequest + +#pragma region IHttpModuleProxy + +void WinRTHttpResource::AddUriHandler(shared_ptr /*uriHandler*/) noexcept /*override*/ +{ + // TODO: Implement custom URI handling. } +void WinRTHttpResource::AddRequestBodyHandler(shared_ptr requestBodyHandler) noexcept /*override*/ +{ + m_requestBodyHandler = weak_ptr(requestBodyHandler); +} + +void WinRTHttpResource::AddResponseHandler(shared_ptr responseHandler) noexcept /*override*/ +{ + m_responseHandler = weak_ptr(responseHandler); +} + +#pragma endregion IHttpModuleProxy + #pragma endregion WinRTHttpResource #pragma region IHttpResource -/*static*/ shared_ptr IHttpResource::Make() noexcept { +/*static*/ shared_ptr IHttpResource::Make( + winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept { + using namespace winrt::Microsoft::ReactNative; + using winrt::Windows::Web::Http::HttpClient; + + shared_ptr result; + if (static_cast(GetRuntimeOptionInt("Http.OriginPolicy")) == OriginPolicy::None) { - return std::make_shared(); + result = std::make_shared(); } else { auto globalOrigin = GetRuntimeOptionString("Http.GlobalOrigin"); OriginPolicyHttpFilter::SetStaticOrigin(std::move(globalOrigin)); auto opFilter = winrt::make(); - auto client = winrt::Windows::Web::Http::HttpClient{opFilter}; + auto client = HttpClient{opFilter}; - return std::make_shared(std::move(client)); + result = std::make_shared(std::move(client)); } + + // Register resource as HTTP module proxy. + if (inspectableProperties) { + auto propId = ReactPropertyId>>{L"HttpModule.Proxy"}; + auto propBag = ReactPropertyBag{inspectableProperties.try_as()}; + auto moduleProxy = weak_ptr{result}; + propBag.Set(propId, std::move(moduleProxy)); + } + + return result; +} + +/*static*/ shared_ptr IHttpResource::Make() noexcept { + auto inspectableProperties = IInspectable{nullptr}; + return Make(inspectableProperties); } #pragma endregion IHttpResource diff --git a/vnext/Shared/Networking/WinRTHttpResource.h b/vnext/Shared/Networking/WinRTHttpResource.h index c9dd8b32da5..44680ca3fb8 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.h +++ b/vnext/Shared/Networking/WinRTHttpResource.h @@ -5,6 +5,7 @@ #include "IHttpResource.h" +#include #include "WinRTTypes.h" // Windows API @@ -15,9 +16,9 @@ namespace Microsoft::React::Networking { - class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_this { - static int64_t s_lastRequestId; - + class WinRTHttpResource : public IHttpResource, + public IHttpModuleProxy, + public std::enable_shared_from_this { winrt::Windows::Web::Http::IHttpClient m_client; std::mutex m_mutex; std::unordered_map m_responses; @@ -28,6 +29,11 @@ namespace Microsoft::React::Networking { std::function m_onDataDynamic; std::function m_onError; + // Used for IHttpModuleProxy + std::weak_ptr m_uriHandler; + std::weak_ptr m_requestBodyHandler; + std::weak_ptr m_responseHandler; + void TrackResponse(int64_t requestId, ResponseOperation response) noexcept; void UntrackResponse(int64_t requestId) noexcept; diff --git a/vnext/Shared/Networking/WinRTTypes.h b/vnext/Shared/Networking/WinRTTypes.h index 4ef2ba4b2c7..1c4d57910fa 100644 --- a/vnext/Shared/Networking/WinRTTypes.h +++ b/vnext/Shared/Networking/WinRTTypes.h @@ -5,6 +5,9 @@ #include "IHttpResource.h" +// Folly +#include + // Windows API #include @@ -16,10 +19,10 @@ namespace Microsoft::React::Networking { struct RequestArgs : public winrt::implements { int64_t RequestId; IHttpResource::Headers Headers; - IHttpResource::BodyData Body; + folly::dynamic Data; bool IncrementalUpdates; bool WithCredentials; - bool IsText; + std::string ResponseType; int64_t Timeout; }; diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index 81471c9d444..aae9034a796 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -71,185 +71,187 @@ using winrt::Microsoft::ReactNative::ReactPropertyBagHelper; namespace Microsoft::React { -/*extern*/ std::unique_ptr CreateHttpModule( - winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept { - if (GetRuntimeOptionBool("Http.UseMonolithicModule")) { - return std::make_unique(); - } else { - return std::make_unique(inspectableProperties); + /*extern*/ std::unique_ptr CreateHttpModule( + winrt::Windows::Foundation::IInspectable const& inspectableProperties) noexcept { + if (GetRuntimeOptionBool("Http.UseMonolithicModule")) { + return std::make_unique(); + } + else { + return std::make_unique(inspectableProperties); + } } -} } // namespace Microsoft::React namespace facebook { -namespace react { - -namespace { - -class OJSIExecutorFactory : public JSExecutorFactory { - public: - std::unique_ptr createJSExecutor( - std::shared_ptr delegate, - std::shared_ptr jsQueue) override { - Logger logger; - if (loggingHook_) { - // TODO :: Ensure the logLevels are mapped properly. - logger = [loggingHook = std::move(loggingHook_)](const std::string &message, unsigned int logLevel) { - loggingHook(static_cast(logLevel), message.c_str()); - }; - } else { - logger = [loggingHook = std::move(loggingHook_)](const std::string & /*message*/, unsigned int /*logLevel*/) {}; - } - bindNativeLogger(*runtimeHolder_->getRuntime(), logger); + namespace react { + + namespace { + + class OJSIExecutorFactory : public JSExecutorFactory { + public: + std::unique_ptr createJSExecutor( + std::shared_ptr delegate, + std::shared_ptr jsQueue) override { + Logger logger; + if (loggingHook_) { + // TODO :: Ensure the logLevels are mapped properly. + logger = [loggingHook = std::move(loggingHook_)](const std::string& message, unsigned int logLevel) { + loggingHook(static_cast(logLevel), message.c_str()); + }; + } + else { + logger = [loggingHook = std::move(loggingHook_)](const std::string& /*message*/, unsigned int /*logLevel*/) {}; + } + bindNativeLogger(*runtimeHolder_->getRuntime(), logger); - auto turboModuleManager = std::make_shared(turboModuleRegistry_, jsCallInvoker_); + auto turboModuleManager = std::make_shared(turboModuleRegistry_, jsCallInvoker_); - // TODO: The binding here should also add the proxys that convert cxxmodules into turbomodules - // [vmoroz] Note, that we must not use the RN TurboCxxModule.h code because it uses global LongLivedObjectCollection - // instance that prevents us from using multiple RN instance in the same process. - auto binding = [turboModuleManager](const std::string &name) -> std::shared_ptr { - return turboModuleManager->getModule(name); - }; + // TODO: The binding here should also add the proxys that convert cxxmodules into turbomodules + // [vmoroz] Note, that we must not use the RN TurboCxxModule.h code because it uses global LongLivedObjectCollection + // instance that prevents us from using multiple RN instance in the same process. + auto binding = [turboModuleManager](const std::string& name) -> std::shared_ptr { + return turboModuleManager->getModule(name); + }; - TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding), longLivedObjectCollection_); + TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding), longLivedObjectCollection_); - // init TurboModule - for (const auto &moduleName : turboModuleManager->getEagerInitModuleNames()) { - turboModuleManager->getModule(moduleName); - } + // init TurboModule + for (const auto& moduleName : turboModuleManager->getEagerInitModuleNames()) { + turboModuleManager->getModule(moduleName); + } - return std::make_unique( - runtimeHolder_->getRuntime(), - std::move(delegate), - JSIExecutor::defaultTimeoutInvoker, - [isProfiling = isProfilingEnabled_]([[maybe_unused]] jsi::Runtime &runtime) { + return std::make_unique( + runtimeHolder_->getRuntime(), + std::move(delegate), + JSIExecutor::defaultTimeoutInvoker, + [isProfiling = isProfilingEnabled_]([[maybe_unused]] jsi::Runtime& runtime) { #ifdef ENABLE_JS_SYSTRACE_TO_ETW - facebook::react::tracing::initializeJSHooks(runtime, isProfiling); + facebook::react::tracing::initializeJSHooks(runtime, isProfiling); #endif - }); - } + }); + } + + OJSIExecutorFactory( + std::shared_ptr runtimeHolder, + NativeLoggingHook loggingHook, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + bool isProfilingEnabled, + std::shared_ptr jsCallInvoker) noexcept + : runtimeHolder_{ std::move(runtimeHolder) }, + loggingHook_{ std::move(loggingHook) }, + turboModuleRegistry_{ std::move(turboModuleRegistry) }, + longLivedObjectCollection_{ std::move(longLivedObjectCollection) }, + jsCallInvoker_{ std::move(jsCallInvoker) }, + isProfilingEnabled_{ isProfilingEnabled } {} + + private: + std::shared_ptr runtimeHolder_; + std::shared_ptr turboModuleRegistry_; + std::shared_ptr longLivedObjectCollection_; + std::shared_ptr jsCallInvoker_; + NativeLoggingHook loggingHook_; + bool isProfilingEnabled_; + }; + + } // namespace + + void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const char* /*tag*/) {} + + /*static*/ std::shared_ptr InstanceImpl::MakeNoBundle( + std::shared_ptr&& instance, + std::string&& jsBundleBasePath, + std::vector< + std::tuple>> + && cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr&& callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept { + auto inner = std::shared_ptr(new InstanceImpl( + std::move(instance), + std::move(jsBundleBasePath), + std::move(cxxModules), + std::move(turboModuleRegistry), + std::move(longLivedObjectCollection), + std::move(callback), + std::move(jsQueue), + std::move(nativeQueue), + std::move(devSettings), + std::move(devManager))); + + inner->RegisterForReloadIfNecessary(); + + return inner; + } + + /*static*/ std::shared_ptr InstanceImpl::MakeAndLoadBundle( + std::shared_ptr&& instance, + std::string&& jsBundleBasePath, + std::string&& jsBundleRelativePath, + std::vector< + std::tuple>> + && cxxModules, + std::shared_ptr turboModuleRegistry, + std::unique_ptr&& callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept { + auto inner = std::shared_ptr(new InstanceImpl( + std::move(instance), + std::move(jsBundleBasePath), + std::move(cxxModules), + std::move(turboModuleRegistry), + nullptr, + std::move(callback), + std::move(jsQueue), + std::move(nativeQueue), + std::move(devSettings), + std::move(devManager))); + + inner->loadBundle(std::move(jsBundleRelativePath)); + inner->RegisterForReloadIfNecessary(); + + return inner; + } - OJSIExecutorFactory( - std::shared_ptr runtimeHolder, - NativeLoggingHook loggingHook, + void InstanceImpl::SetInError() noexcept { + m_isInError = true; + } + + namespace { + bool shouldStartHermesInspector(DevSettings& devSettings) { + bool isHermes = + ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || + (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && + devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); + + if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) + return true; + else + return false; + } + } // namespace + + InstanceImpl::InstanceImpl( + std::shared_ptr&& instance, + std::string&& jsBundleBasePath, + std::vector< + std::tuple>> + && cxxModules, std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - bool isProfilingEnabled, - std::shared_ptr jsCallInvoker) noexcept - : runtimeHolder_{std::move(runtimeHolder)}, - loggingHook_{std::move(loggingHook)}, - turboModuleRegistry_{std::move(turboModuleRegistry)}, - longLivedObjectCollection_{std::move(longLivedObjectCollection)}, - jsCallInvoker_{std::move(jsCallInvoker)}, - isProfilingEnabled_{isProfilingEnabled} {} - - private: - std::shared_ptr runtimeHolder_; - std::shared_ptr turboModuleRegistry_; - std::shared_ptr longLivedObjectCollection_; - std::shared_ptr jsCallInvoker_; - NativeLoggingHook loggingHook_; - bool isProfilingEnabled_; -}; - -} // namespace - -void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const char * /*tag*/) {} - -/*static*/ std::shared_ptr InstanceImpl::MakeNoBundle( - std::shared_ptr &&instance, - std::string &&jsBundleBasePath, - std::vector< - std::tuple>> - &&cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept { - auto inner = std::shared_ptr(new InstanceImpl( - std::move(instance), - std::move(jsBundleBasePath), - std::move(cxxModules), - std::move(turboModuleRegistry), - std::move(longLivedObjectCollection), - std::move(callback), - std::move(jsQueue), - std::move(nativeQueue), - std::move(devSettings), - std::move(devManager))); - - inner->RegisterForReloadIfNecessary(); - - return inner; -} - -/*static*/ std::shared_ptr InstanceImpl::MakeAndLoadBundle( - std::shared_ptr &&instance, - std::string &&jsBundleBasePath, - std::string &&jsBundleRelativePath, - std::vector< - std::tuple>> - &&cxxModules, - std::shared_ptr turboModuleRegistry, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept { - auto inner = std::shared_ptr(new InstanceImpl( - std::move(instance), - std::move(jsBundleBasePath), - std::move(cxxModules), - std::move(turboModuleRegistry), - nullptr, - std::move(callback), - std::move(jsQueue), - std::move(nativeQueue), - std::move(devSettings), - std::move(devManager))); - - inner->loadBundle(std::move(jsBundleRelativePath)); - inner->RegisterForReloadIfNecessary(); - - return inner; -} - -void InstanceImpl::SetInError() noexcept { - m_isInError = true; -} - -namespace { -bool shouldStartHermesInspector(DevSettings &devSettings) { - bool isHermes = - ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || - (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && - devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); - - if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) - return true; - else - return false; -} -} // namespace - -InstanceImpl::InstanceImpl( - std::shared_ptr &&instance, - std::string &&jsBundleBasePath, - std::vector< - std::tuple>> - &&cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) - : m_turboModuleRegistry(std::move(turboModuleRegistry)), + std::shared_ptr longLivedObjectCollection, + std::unique_ptr&& callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) + : m_turboModuleRegistry(std::move(turboModuleRegistry)), m_longLivedObjectCollection(std::move(longLivedObjectCollection)), m_jsThread(std::move(jsQueue)), m_nativeQueue(nativeQueue), @@ -257,422 +259,428 @@ InstanceImpl::InstanceImpl( m_devSettings(std::move(devSettings)), m_devManager(std::move(devManager)), m_innerInstance(std::move(instance)) { - // Temp set the logmarker here - facebook::react::ReactMarker::logTaggedMarker = logMarker; + // Temp set the logmarker here + facebook::react::ReactMarker::logTaggedMarker = logMarker; #ifdef ENABLE_ETW_TRACING - // TODO :: Find a better place to initialize ETW once per process. - facebook::react::tracing::initializeETW(); + // TODO :: Find a better place to initialize ETW once per process. + facebook::react::tracing::initializeETW(); #endif - if (shouldStartHermesInspector(*m_devSettings)) { - m_devManager->StartInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort); - } - - // Default (common) NativeModules - auto modules = GetDefaultNativeModules(nativeQueue); + if (shouldStartHermesInspector(*m_devSettings)) { + m_devManager->StartInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort); + } - // Add app provided modules. - for (auto &cxxModule : cxxModules) { - modules.push_back(std::make_unique( - m_innerInstance, move(std::get<0>(cxxModule)), move(std::get<1>(cxxModule)), move(std::get<2>(cxxModule)))); - } - m_moduleRegistry = std::make_shared(std::move(modules)); - - // Choose JSExecutor - std::shared_ptr jsef; - if (m_devSettings->useWebDebugger) { - try { - auto jseFunc = m_devManager->LoadJavaScriptInProxyMode(*m_devSettings, [weakthis = weak_from_this()]() { - if (auto strongThis = weakthis.lock()) { - strongThis->SetInError(); - } - }); + // Default (common) NativeModules + auto modules = GetDefaultNativeModules(nativeQueue); - if ((jseFunc == nullptr) || m_isInError) { - m_devSettings->errorCallback("Failed to create JavaScript Executor."); - return; + // Add app provided modules. + for (auto& cxxModule : cxxModules) { + modules.push_back(std::make_unique( + m_innerInstance, move(std::get<0>(cxxModule)), move(std::get<1>(cxxModule)), move(std::get<2>(cxxModule)))); } + m_moduleRegistry = std::make_shared(std::move(modules)); + + // Choose JSExecutor + std::shared_ptr jsef; + if (m_devSettings->useWebDebugger) { + try { + auto jseFunc = m_devManager->LoadJavaScriptInProxyMode(*m_devSettings, [weakthis = weak_from_this()]() { + if (auto strongThis = weakthis.lock()) { + strongThis->SetInError(); + } + }); + + if ((jseFunc == nullptr) || m_isInError) { + m_devSettings->errorCallback("Failed to create JavaScript Executor."); + return; + } - jsef = std::make_shared(std::move(jseFunc)); - } catch (std::exception &e) { - m_devSettings->errorCallback(e.what()); - return; - } - } else { - if (m_devSettings->useFastRefresh || m_devSettings->liveReloadCallback) { - Microsoft::ReactNative::PackagerConnection::CreateOrReusePackagerConnection(*m_devSettings); - } + jsef = std::make_shared(std::move(jseFunc)); + } + catch (std::exception& e) { + m_devSettings->errorCallback(e.what()); + return; + } + } + else { + if (m_devSettings->useFastRefresh || m_devSettings->liveReloadCallback) { + Microsoft::ReactNative::PackagerConnection::CreateOrReusePackagerConnection(*m_devSettings); + } - // If the consumer gives us a JSI runtime, then use it. - if (m_devSettings->jsiRuntimeHolder) { - assert(m_devSettings->jsiEngineOverride == JSIEngineOverride::Default); - jsef = std::make_shared( - m_devSettings->jsiRuntimeHolder, - m_devSettings->loggingCallback, - m_turboModuleRegistry, - m_longLivedObjectCollection, - !m_devSettings->useFastRefresh, - m_innerInstance->getJSCallInvoker()); - } else { - assert(m_devSettings->jsiEngineOverride != JSIEngineOverride::Default); - switch (m_devSettings->jsiEngineOverride) { - case JSIEngineOverride::Hermes: - m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread); - break; - case JSIEngineOverride::V8: { + // If the consumer gives us a JSI runtime, then use it. + if (m_devSettings->jsiRuntimeHolder) { + assert(m_devSettings->jsiEngineOverride == JSIEngineOverride::Default); + jsef = std::make_shared( + m_devSettings->jsiRuntimeHolder, + m_devSettings->loggingCallback, + m_turboModuleRegistry, + m_longLivedObjectCollection, + !m_devSettings->useFastRefresh, + m_innerInstance->getJSCallInvoker()); + } + else { + assert(m_devSettings->jsiEngineOverride != JSIEngineOverride::Default); + switch (m_devSettings->jsiEngineOverride) { + case JSIEngineOverride::Hermes: + m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread); + break; + case JSIEngineOverride::V8: { #if defined(USE_V8) - std::unique_ptr scriptStore = nullptr; - std::unique_ptr preparedScriptStore = nullptr; + std::unique_ptr scriptStore = nullptr; + std::unique_ptr preparedScriptStore = nullptr; - char tempPath[MAX_PATH]; - if (GetTempPathA(MAX_PATH, tempPath)) { - preparedScriptStore = std::make_unique(tempPath); - } + char tempPath[MAX_PATH]; + if (GetTempPathA(MAX_PATH, tempPath)) { + preparedScriptStore = std::make_unique(tempPath); + } - m_devSettings->jsiRuntimeHolder = std::make_shared( + m_devSettings->jsiRuntimeHolder = std::make_shared( m_devSettings, m_jsThread, std::move(scriptStore), std::move(preparedScriptStore)); - break; + break; #else - assert(false); // V8 is not available in this build, fallthrough - [[fallthrough]]; + assert(false); // V8 is not available in this build, fallthrough + [[fallthrough]]; #endif - } - case JSIEngineOverride::V8NodeApi: { + } + case JSIEngineOverride::V8NodeApi: { #if defined(USE_V8) - std::unique_ptr preparedScriptStore; + std::unique_ptr preparedScriptStore; - wchar_t tempPath[MAX_PATH]; - if (GetTempPathW(static_cast(std::size(tempPath)), tempPath)) { - preparedScriptStore = + wchar_t tempPath[MAX_PATH]; + if (GetTempPathW(static_cast(std::size(tempPath)), tempPath)) { + preparedScriptStore = std::make_unique(winrt::to_string(tempPath)); - } + } - if (!preparedScriptStore) { - if (m_devSettings->errorCallback) - m_devSettings->errorCallback("Could not initialize prepared script store"); + if (!preparedScriptStore) { + if (m_devSettings->errorCallback) + m_devSettings->errorCallback("Could not initialize prepared script store"); - break; - } + break; + } - m_devSettings->jsiRuntimeHolder = make_shared( + m_devSettings->jsiRuntimeHolder = make_shared( m_devSettings, m_jsThread, nullptr /*scriptStore*/, std::move(preparedScriptStore)); - break; + break; #else - if (m_devSettings->errorCallback) - m_devSettings->errorCallback("JSI/V8/NAPI engine is not available in this build"); - assert(false); - [[fallthrough]]; + if (m_devSettings->errorCallback) + m_devSettings->errorCallback("JSI/V8/NAPI engine is not available in this build"); + assert(false); + [[fallthrough]]; #endif - } - case JSIEngineOverride::Chakra: - case JSIEngineOverride::ChakraCore: - default: // TODO: Add other engines once supported - m_devSettings->jsiRuntimeHolder = + } + case JSIEngineOverride::Chakra: + case JSIEngineOverride::ChakraCore: + default: // TODO: Add other engines once supported + m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread, nullptr, nullptr); - break; + break; + } + jsef = std::make_shared( + m_devSettings->jsiRuntimeHolder, + m_devSettings->loggingCallback, + m_turboModuleRegistry, + m_longLivedObjectCollection, + !m_devSettings->useFastRefresh, + m_innerInstance->getJSCallInvoker()); + } + } + + m_innerInstance->initializeBridge(std::move(callback), jsef, m_jsThread, m_moduleRegistry); + + // All JSI runtimes do support host objects and hence the native modules + // proxy. + const bool isNativeModulesProxyAvailable = ((m_devSettings->jsiRuntimeHolder != nullptr) || + (m_devSettings->jsiEngineOverride != JSIEngineOverride::Default)) && + !m_devSettings->useWebDebugger; + if (!isNativeModulesProxyAvailable) { + folly::dynamic configArray = folly::dynamic::array; + for (auto const& moduleName : m_moduleRegistry->moduleNames()) { + auto moduleConfig = m_moduleRegistry->getConfig(moduleName); + configArray.push_back(moduleConfig ? std::move(moduleConfig->config) : nullptr); + } + + folly::dynamic configs = folly::dynamic::object("remoteModuleConfig", std::move(configArray)); + m_innerInstance->setGlobalVariable( + "__fbBatchedBridgeConfig", std::make_unique(folly::toJson(configs))); } - jsef = std::make_shared( - m_devSettings->jsiRuntimeHolder, - m_devSettings->loggingCallback, - m_turboModuleRegistry, - m_longLivedObjectCollection, - !m_devSettings->useFastRefresh, - m_innerInstance->getJSCallInvoker()); } - } - m_innerInstance->initializeBridge(std::move(callback), jsef, m_jsThread, m_moduleRegistry); - - // All JSI runtimes do support host objects and hence the native modules - // proxy. - const bool isNativeModulesProxyAvailable = ((m_devSettings->jsiRuntimeHolder != nullptr) || - (m_devSettings->jsiEngineOverride != JSIEngineOverride::Default)) && - !m_devSettings->useWebDebugger; - if (!isNativeModulesProxyAvailable) { - folly::dynamic configArray = folly::dynamic::array; - for (auto const &moduleName : m_moduleRegistry->moduleNames()) { - auto moduleConfig = m_moduleRegistry->getConfig(moduleName); - configArray.push_back(moduleConfig ? std::move(moduleConfig->config) : nullptr); + void InstanceImpl::loadBundle(std::string&& jsBundleRelativePath) { + loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ false); } - folly::dynamic configs = folly::dynamic::object("remoteModuleConfig", std::move(configArray)); - m_innerInstance->setGlobalVariable( - "__fbBatchedBridgeConfig", std::make_unique(folly::toJson(configs))); - } -} - -void InstanceImpl::loadBundle(std::string &&jsBundleRelativePath) { - loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ false); -} - -void InstanceImpl::loadBundleSync(std::string &&jsBundleRelativePath) { - loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ true); -} - -// Note: Based on -// https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactCommon/cxxreact/Instance.cpp#L112 -bool isHBCBundle(const std::string &bundle) { - static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3; - - // Note:: Directly access the pointer to avoid copy/length-check. It matters as this string contains the bundle which - // can be potentially huge. - // https://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/#comment-483 - auto header = reinterpret_cast(&bundle[0]); - if (HBCBundleMagicNumber == folly::Endian::little(header->magic)) { - return true; - } else { - return false; - } -} + void InstanceImpl::loadBundleSync(std::string&& jsBundleRelativePath) { + loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ true); + } -void InstanceImpl::loadBundleInternal(std::string &&jsBundleRelativePath, bool synchronously) { - try { - if (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback != nullptr || - m_devSettings->useFastRefresh) { - // First attempt to get download the Js locally, to catch any bundling - // errors before attempting to load the actual script. + // Note: Based on + // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactCommon/cxxreact/Instance.cpp#L112 + bool isHBCBundle(const std::string& bundle) { + static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3; + + // Note:: Directly access the pointer to avoid copy/length-check. It matters as this string contains the bundle which + // can be potentially huge. + // https://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/#comment-483 + auto header = reinterpret_cast(&bundle[0]); + if (HBCBundleMagicNumber == folly::Endian::little(header->magic)) { + return true; + } + else { + return false; + } + } - uint32_t hermesBytecodeVersion = 0; + void InstanceImpl::loadBundleInternal(std::string&& jsBundleRelativePath, bool synchronously) { + try { + if (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback != nullptr || + m_devSettings->useFastRefresh) { + // First attempt to get download the Js locally, to catch any bundling + // errors before attempting to load the actual script. + + uint32_t hermesBytecodeVersion = 0; #if defined(USE_HERMES) && defined(ENABLE_DEVSERVER_HBCBUNDLES) - hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; + hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; #endif - auto [jsBundleString, success] = Microsoft::ReactNative::GetJavaScriptFromServer( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, - m_devSettings->platformName, - true /* dev */, - m_devSettings->useFastRefresh, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion); + auto [jsBundleString, success] = Microsoft::ReactNative::GetJavaScriptFromServer( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, + m_devSettings->platformName, + true /* dev */, + m_devSettings->useFastRefresh, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion); - if (!success) { - m_devManager->UpdateBundleStatus(false, -1); - m_devSettings->errorCallback(jsBundleString); - return; - } + if (!success) { + m_devManager->UpdateBundleStatus(false, -1); + m_devSettings->errorCallback(jsBundleString); + return; + } - int64_t currentTimeInMilliSeconds = - std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) - .count(); - m_devManager->UpdateBundleStatus(true, currentTimeInMilliSeconds); + int64_t currentTimeInMilliSeconds = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); + m_devManager->UpdateBundleStatus(true, currentTimeInMilliSeconds); - auto bundleUrl = DevServerHelper::get_BundleUrl( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, - m_devSettings->platformName, - /*dev*/ true, - /*hot*/ false, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion); - - // This code is based on the HBC Bundle integration on Android - // Ref: - // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp#L231 - if (isHBCBundle(jsBundleString)) { - auto script = std::make_unique(jsBundleString, false); - const char *buffer = script->c_str(); - uint32_t bufferLength = (uint32_t)script->size(); - - // Please refer the code here for details on the file format: - // https://github.com/facebook/metro/blob/b1bacf52070be62872d6bd3420f37a4405ed34e6/packages/metro/src/lib/bundleToBytecode.js#L29 - // Essentially, there is an 8 byte long file header with 4 bytes of a magic number followed by 4 bytes to encode - // the number of modules.The module buffers follows, each one starts with 4 byte header which encodes module - // length.A properly formatted HBCB should have at least 8 bytes.. - uint32_t offset = 8; + auto bundleUrl = DevServerHelper::get_BundleUrl( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, + m_devSettings->platformName, + /*dev*/ true, + /*hot*/ false, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion); + + // This code is based on the HBC Bundle integration on Android + // Ref: + // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp#L231 + if (isHBCBundle(jsBundleString)) { + auto script = std::make_unique(jsBundleString, false); + const char* buffer = script->c_str(); + uint32_t bufferLength = (uint32_t)script->size(); + + // Please refer the code here for details on the file format: + // https://github.com/facebook/metro/blob/b1bacf52070be62872d6bd3420f37a4405ed34e6/packages/metro/src/lib/bundleToBytecode.js#L29 + // Essentially, there is an 8 byte long file header with 4 bytes of a magic number followed by 4 bytes to encode + // the number of modules.The module buffers follows, each one starts with 4 byte header which encodes module + // length.A properly formatted HBCB should have at least 8 bytes.. + uint32_t offset = 8; #define __SAFEADD__(s1, s2, t) \ if (!msl::utilities::SafeAdd(s1, s2, t)) \ break; - while (offset < bufferLength) { - uint32_t segment; - __SAFEADD__(offset, 4, segment) - uint32_t moduleLength = (bufferLength < segment) ? 0 : *(((uint32_t *)buffer) + offset / 4); - - // Early break if the module length is computed as 0.. as the segment start may be overflowing the buffer. - if (moduleLength == 0) - break; - - uint32_t segmentEnd; - __SAFEADD__(moduleLength, segment, segmentEnd) - // Early break if the segment overflows beyond the buffer. This is unlikely for a properly formatted - // HBCB though. - if (segmentEnd > bufferLength) - break; - - m_innerInstance->loadScriptFromString( - std::make_unique(std::string(buffer + segment, buffer + segmentEnd)), + while (offset < bufferLength) { + uint32_t segment; + __SAFEADD__(offset, 4, segment) + uint32_t moduleLength = (bufferLength < segment) ? 0 : *(((uint32_t*)buffer) + offset / 4); + + // Early break if the module length is computed as 0.. as the segment start may be overflowing the buffer. + if (moduleLength == 0) + break; + + uint32_t segmentEnd; + __SAFEADD__(moduleLength, segment, segmentEnd) + // Early break if the segment overflows beyond the buffer. This is unlikely for a properly formatted + // HBCB though. + if (segmentEnd > bufferLength) + break; + + m_innerInstance->loadScriptFromString( + std::make_unique(std::string(buffer + segment, buffer + segmentEnd)), + bundleUrl, + false); + + // Aligned at 4 byte boundary. + offset += ((moduleLength + 3) & ~3) + 4; + } +#undef __SAFEADD__ + } + else { + // Remote debug executor loads script from a Uri, rather than taking the actual bundle string + m_innerInstance->loadScriptFromString( + std::make_unique(m_devSettings->useWebDebugger ? bundleUrl : jsBundleString), bundleUrl, - false); - - // Aligned at 4 byte boundary. - offset += ((moduleLength + 3) & ~3) + 4; + synchronously); + } } -#undef __SAFEADD__ - } else { - // Remote debug executor loads script from a Uri, rather than taking the actual bundle string - m_innerInstance->loadScriptFromString( - std::make_unique(m_devSettings->useWebDebugger ? bundleUrl : jsBundleString), - bundleUrl, - synchronously); - } - } else { + else { #if (defined(_MSC_VER) && !defined(WINRT)) - std::string bundlePath = (fs::path(m_devSettings->bundleRootPath) / jsBundleRelativePath).string(); - auto bundleString = FileMappingBigString::fromPath(bundlePath); + std::string bundlePath = (fs::path(m_devSettings->bundleRootPath) / jsBundleRelativePath).string(); + auto bundleString = FileMappingBigString::fromPath(bundlePath); #else - std::string bundlePath; - if (m_devSettings->bundleRootPath._Starts_with("resource://")) { - auto uri = winrt::Windows::Foundation::Uri( - winrt::to_hstring(m_devSettings->bundleRootPath), winrt::to_hstring(jsBundleRelativePath)); - bundlePath = winrt::to_string(uri.ToString()); - } else { - bundlePath = (fs::path(m_devSettings->bundleRootPath) / (jsBundleRelativePath + ".bundle")).string(); - } + std::string bundlePath; + if (m_devSettings->bundleRootPath._Starts_with("resource://")) { + auto uri = winrt::Windows::Foundation::Uri( + winrt::to_hstring(m_devSettings->bundleRootPath), winrt::to_hstring(jsBundleRelativePath)); + bundlePath = winrt::to_string(uri.ToString()); + } + else { + bundlePath = (fs::path(m_devSettings->bundleRootPath) / (jsBundleRelativePath + ".bundle")).string(); + } - auto bundleString = std::make_unique<::Microsoft::ReactNative::StorageFileBigString>(bundlePath); + auto bundleString = std::make_unique<::Microsoft::ReactNative::StorageFileBigString>(bundlePath); #endif - m_innerInstance->loadScriptFromString(std::move(bundleString), std::move(jsBundleRelativePath), synchronously); + m_innerInstance->loadScriptFromString(std::move(bundleString), std::move(jsBundleRelativePath), synchronously); + } + } + catch (const std::exception& e) { + m_devSettings->errorCallback(e.what()); + } + catch (const winrt::hresult_error& hrerr) { + auto error = fmt::format("[0x{:0>8x}] {}", static_cast(hrerr.code()), winrt::to_string(hrerr.message())); + + m_devSettings->errorCallback(std::move(error)); + } } - } catch (const std::exception &e) { - m_devSettings->errorCallback(e.what()); - } catch (const winrt::hresult_error &hrerr) { - auto error = fmt::format("[0x{:0>8x}] {}", static_cast(hrerr.code()), winrt::to_string(hrerr.message())); - m_devSettings->errorCallback(std::move(error)); - } -} + InstanceImpl::~InstanceImpl() { + if (m_devSettings->jsiEngineOverride == JSIEngineOverride::Hermes) { + m_devManager->StopInspector(); + } + m_nativeQueue->quitSynchronous(); + } -InstanceImpl::~InstanceImpl() { - if (m_devSettings->jsiEngineOverride == JSIEngineOverride::Hermes) { - m_devManager->StopInspector(); - } - m_nativeQueue->quitSynchronous(); -} - -std::vector> InstanceImpl::GetDefaultNativeModules( - std::shared_ptr nativeQueue) { - std::vector> modules; - auto transitionalProps{ReactPropertyBagHelper::CreatePropertyBag()}; - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetHttpModuleName(), - [nativeQueue, transitionalProps]() -> std::unique_ptr { - return Microsoft::React::CreateHttpModule(transitionalProps); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetWebSocketModuleName(), - [nativeQueue, transitionalProps]() -> std::unique_ptr { - return Microsoft::React::CreateWebSocketModule(transitionalProps); - }, - nativeQueue)); - - // TODO: This is not included for UWP because we have a different module which - // is added later. However, this one is designed - // so that we can base a UWP version on it. We need to do that but is not high - // priority. + std::vector> InstanceImpl::GetDefaultNativeModules( + std::shared_ptr nativeQueue) { + std::vector> modules; + auto transitionalProps{ ReactPropertyBagHelper::CreatePropertyBag() }; + + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetHttpModuleName(), + [nativeQueue, transitionalProps]() -> std::unique_ptr { + return Microsoft::React::CreateHttpModule(transitionalProps); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetWebSocketModuleName(), + [nativeQueue, transitionalProps]() -> std::unique_ptr { + return Microsoft::React::CreateWebSocketModule(transitionalProps); + }, + nativeQueue)); + + // TODO: This is not included for UWP because we have a different module which + // is added later. However, this one is designed + // so that we can base a UWP version on it. We need to do that but is not high + // priority. #if (defined(_MSC_VER) && !defined(WINRT)) - modules.push_back(std::make_unique( - m_innerInstance, - "Timing", - [nativeQueue]() -> std::unique_ptr { return react::CreateTimingModule(nativeQueue); }, - nativeQueue)); + modules.push_back(std::make_unique( + m_innerInstance, + "Timing", + [nativeQueue]() -> std::unique_ptr { return react::CreateTimingModule(nativeQueue); }, + nativeQueue)); #endif - uint32_t hermesBytecodeVersion = 0; + uint32_t hermesBytecodeVersion = 0; #if defined(USE_HERMES) && defined(ENABLE_DEVSERVER_HBCBUNDLES) - hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; + hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; #endif - // TODO - Encapsulate this in a helpers, and make sure callers add it to their - // list - std::string bundleUrl = (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback) - ? DevServerHelper::get_BundleUrl( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath, - m_devSettings->platformName, - true /*dev*/, - m_devSettings->useFastRefresh, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion) - : std::string(); - modules.push_back(std::make_unique( - m_innerInstance, - facebook::react::SourceCodeModule::Name, - [bundleUrl]() -> std::unique_ptr { - return std::make_unique(bundleUrl); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - "ExceptionsManager", - [redboxHandler = m_devSettings->redboxHandler]() mutable { - return std::make_unique(redboxHandler); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - PlatformConstantsModule::Name, - []() { return std::make_unique(); }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - StatusBarManagerModule::Name, - []() { return std::make_unique(); }, - nativeQueue)); - - // #10036 - Blob module not supported in UWP. Need to define property bag lifetime and onwership. - if (Microsoft::React::GetRuntimeOptionBool("Blob.EnableModule")) { - modules.push_back(std::make_unique( + // TODO - Encapsulate this in a helpers, and make sure callers add it to their + // list + std::string bundleUrl = (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback) + ? DevServerHelper::get_BundleUrl( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath, + m_devSettings->platformName, + true /*dev*/, + m_devSettings->useFastRefresh, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion) + : std::string(); + modules.push_back(std::make_unique( + m_innerInstance, + facebook::react::SourceCodeModule::Name, + [bundleUrl]() -> std::unique_ptr { + return std::make_unique(bundleUrl); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + "ExceptionsManager", + [redboxHandler = m_devSettings->redboxHandler]() mutable { + return std::make_unique(redboxHandler); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + PlatformConstantsModule::Name, + []() { return std::make_unique(); }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + StatusBarManagerModule::Name, + []() { return std::make_unique(); }, + nativeQueue)); + + modules.push_back(std::make_unique( m_innerInstance, Microsoft::React::GetBlobModuleName(), [transitionalProps]() { return Microsoft::React::CreateBlobModule(transitionalProps); }, nativeQueue)); - modules.push_back(std::make_unique( + modules.push_back(std::make_unique( m_innerInstance, Microsoft::React::GetFileReaderModuleName(), [transitionalProps]() { return Microsoft::React::CreateFileReaderModule(transitionalProps); }, nativeQueue)); - } - return modules; -} + return modules; + } -void InstanceImpl::RegisterForReloadIfNecessary() noexcept { - // setup polling for live reload - if (!m_isInError && !m_devSettings->useFastRefresh && m_devSettings->liveReloadCallback != nullptr) { - m_devManager->StartPollingLiveReload( - m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort, m_devSettings->liveReloadCallback); - } -} + void InstanceImpl::RegisterForReloadIfNecessary() noexcept { + // setup polling for live reload + if (!m_isInError && !m_devSettings->useFastRefresh && m_devSettings->liveReloadCallback != nullptr) { + m_devManager->StartPollingLiveReload( + m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort, m_devSettings->liveReloadCallback); + } + } -void InstanceImpl::DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic &&eventData) { - if (m_isInError) { - return; - } + void InstanceImpl::DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic&& eventData) { + if (m_isInError) { + return; + } - folly::dynamic params = folly::dynamic::array(viewTag, eventName, std::move(eventData)); - m_innerInstance->callJSFunction("RCTEventEmitter", "receiveEvent", std::move(params)); -} + folly::dynamic params = folly::dynamic::array(viewTag, eventName, std::move(eventData)); + m_innerInstance->callJSFunction("RCTEventEmitter", "receiveEvent", std::move(params)); + } -void InstanceImpl::invokeCallback(const int64_t callbackId, folly::dynamic &¶ms) { - if (m_isInError) { - return; - } + void InstanceImpl::invokeCallback(const int64_t callbackId, folly::dynamic&& params) { + if (m_isInError) { + return; + } - m_innerInstance->callJSCallback(callbackId, std::move(params)); -} + m_innerInstance->callJSCallback(callbackId, std::move(params)); + } -} // namespace react + } // namespace react } // namespace facebook diff --git a/vnext/Shared/OInstance.h b/vnext/Shared/OInstance.h index d09e2d25353..a85bad13e24 100644 --- a/vnext/Shared/OInstance.h +++ b/vnext/Shared/OInstance.h @@ -10,7 +10,6 @@ #include "InstanceManager.h" // React Native -#include #include // Standard Libriary @@ -19,87 +18,87 @@ #include namespace facebook { -namespace react { - -struct IDevSupportManager; -struct IReactRootView; - -class InstanceImpl final : public InstanceWrapper, private ::std::enable_shared_from_this { - public: - static std::shared_ptr MakeNoBundle( - std::shared_ptr &&instance, - std::string &&jsBundleBasePath, - std::vector< - std::tuple>> - &&cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept; - - static std::shared_ptr MakeAndLoadBundle( - std::shared_ptr &&instance, - std::string &&jsBundleBasePath, - std::string &&jsBundleRelativePath, - std::vector< - std::tuple>> - &&cxxModules, - std::shared_ptr turboModuleRegistry, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept; - - // Instance methods - void loadBundle(std::string &&jsBundleRelativePath) override; - void loadBundleSync(std::string &&jsBundleRelativePath) override; - virtual const std::shared_ptr &GetInstance() const noexcept override { - return m_innerInstance; - } - - void DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic &&eventData) override; - void invokeCallback(const int64_t callbackId, folly::dynamic &¶ms) override; - - ~InstanceImpl(); - - private: - InstanceImpl( - std::shared_ptr &&instance, - std::string &&jsBundleFile, - std::vector< - std::tuple>> - &&cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager); - - std::vector> GetDefaultNativeModules(std::shared_ptr nativeQueue); - void RegisterForReloadIfNecessary() noexcept; - void loadBundleInternal(std::string &&jsBundleRelativePath, bool synchronously); - void SetInError() noexcept; - - private: - std::shared_ptr m_innerInstance; - - std::string m_jsBundleBasePath; - std::shared_ptr m_moduleRegistry; - std::shared_ptr m_turboModuleRegistry; - std::shared_ptr m_longLivedObjectCollection; - std::shared_ptr m_jsThread; - std::shared_ptr m_nativeQueue; - - std::shared_ptr m_devManager; - std::shared_ptr m_devSettings; - bool m_isInError{false}; -}; - -} // namespace react + namespace react { + + struct IDevSupportManager; + struct IReactRootView; + + class InstanceImpl final : public InstanceWrapper, private ::std::enable_shared_from_this { + public: + static std::shared_ptr MakeNoBundle( + std::shared_ptr&& instance, + std::string&& jsBundleBasePath, + std::vector< + std::tuple>> + && cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr&& callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept; + + static std::shared_ptr MakeAndLoadBundle( + std::shared_ptr&& instance, + std::string&& jsBundleBasePath, + std::string&& jsBundleRelativePath, + std::vector< + std::tuple>> + && cxxModules, + std::shared_ptr turboModuleRegistry, + std::unique_ptr&& callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept; + + // Instance methods + void loadBundle(std::string&& jsBundleRelativePath) override; + void loadBundleSync(std::string&& jsBundleRelativePath) override; + virtual const std::shared_ptr& GetInstance() const noexcept override { + return m_innerInstance; + } + + void DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic&& eventData) override; + void invokeCallback(const int64_t callbackId, folly::dynamic&& params) override; + + ~InstanceImpl(); + + private: + InstanceImpl( + std::shared_ptr&& instance, + std::string&& jsBundleFile, + std::vector< + std::tuple>> + && cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr&& callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager); + + std::vector> GetDefaultNativeModules(std::shared_ptr nativeQueue); + void RegisterForReloadIfNecessary() noexcept; + void loadBundleInternal(std::string&& jsBundleRelativePath, bool synchronously); + void SetInError() noexcept; + + private: + std::shared_ptr m_innerInstance; + + std::string m_jsBundleBasePath; + std::shared_ptr m_moduleRegistry; + std::shared_ptr m_turboModuleRegistry; + std::shared_ptr m_longLivedObjectCollection; + std::shared_ptr m_jsThread; + std::shared_ptr m_nativeQueue; + + std::shared_ptr m_devManager; + std::shared_ptr m_devSettings; + bool m_isInError{ false }; + }; + + } // namespace react } // namespace facebook diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index c1ca1617ded..0a9696f3415 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -130,6 +130,9 @@ Source Files + + Source Files\Modules + Source Files\Modules @@ -145,6 +148,13 @@ Source Files\Networking + + + Source Files\Modules + + + Source Files\Modules + @@ -390,6 +400,15 @@ Header Files + + Header Files\Modules + + + Header Files\Modules + + + Header Files\Modules + Header Files\Modules @@ -420,6 +439,27 @@ Header Files + + Header Files\Modules + + + Header Files\Modules + + + Header Files\Modules + + + Header Files\Modules + + + Header Files\Modules + + + Header Files\Modules + + + Header Files\Modules + diff --git a/vnext/overrides.json b/vnext/overrides.json index 187abe5b1cd..f37c7ed5ce9 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -90,6 +90,10 @@ "type": "platform", "file": "src/IntegrationTests/BlobTest.js" }, + { + "type": "platform", + "file": "src/IntegrationTests/BlobTest.js" + }, { "type": "platform", "file": "src/IntegrationTests/DummyTest.js" @@ -479,4 +483,4 @@ "file": "src/typings-index.ts" } ] -} \ No newline at end of file +} diff --git a/vnext/src/IntegrationTests/BlobTest.js b/vnext/src/IntegrationTests/BlobTest.js index 415aa95b2ea..3518b5276eb 100644 --- a/vnext/src/IntegrationTests/BlobTest.js +++ b/vnext/src/IntegrationTests/BlobTest.js @@ -11,7 +11,7 @@ const ReactNative = require('react-native'); const {AppRegistry, View} = ReactNative; -const {TestModule, FileReader} = ReactNative.NativeModules; +const {TestModule} = ReactNative.NativeModules; type State = { statusCode: number, diff --git a/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js b/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js index 4df783c41a6..7fb46a949d2 100644 --- a/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js +++ b/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js @@ -14,6 +14,7 @@ /* eslint-env node */ const WebSocket = require('ws'); +const Blob = require('node-fetch'); console.log(`\ WebSocket binary integration test server From f5a3f6d49d95535b35b2df58af3cbca83d0b14fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Thu, 9 Jun 2022 20:07:37 -0700 Subject: [PATCH 05/23] Use logical OR to assert HTTP responseType (#10095) * update yarn.lock * Use logical OR to assert HTTP responseType * Change files --- ...ative-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json | 7 +++++++ vnext/Shared/Networking/WinRTHttpResource.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 change/react-native-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json diff --git a/change/react-native-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json b/change/react-native-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json new file mode 100644 index 00000000000..e825cb14dcb --- /dev/null +++ b/change/react-native-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Use logical OR to assert HTTP responseType", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index 388d75e8c89..a496186d769 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -68,7 +68,7 @@ void WinRTHttpResource::SendRequest( bool withCredentials, std::function &&callback) noexcept /*override*/ { // Enforce supported args - assert(responseType == "text" || responseType == "base64" | responseType == "blob"); + assert(responseType == "text" || responseType == "base64" || responseType == "blob"); if (callback) { callback(requestId); From e4f5e1be8db46161481229cc3beb30bb6a0aa357 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 15:57:46 -0700 Subject: [PATCH 06/23] Remove change files --- ...ative-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 change/react-native-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json diff --git a/change/react-native-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json b/change/react-native-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json deleted file mode 100644 index e825cb14dcb..00000000000 --- a/change/react-native-windows-1653495d-e9db-497e-a6e2-bb5fbcc05317.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "prerelease", - "comment": "Use logical OR to assert HTTP responseType", - "packageName": "react-native-windows", - "email": "julio.rocha@microsoft.com", - "dependentChangeType": "patch" -} From eb4e709d44f45859768be1b5ba08db373b1d9c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Mon, 18 Jul 2022 17:13:40 -0700 Subject: [PATCH 07/23] Implement HTTP client timeout (#10261) * Implement hard-coded timeout * Create timeout from JS args * Timeout only for values greater than 0 * Change files * Remove variable sendRequestAny * Remove unused captures --- ...-42dec614-af90-4b29-b8c7-f266415ea240.json | 7 ++ .../HttpOriginPolicyIntegrationTest.cpp | 6 +- .../HttpResourceIntegrationTests.cpp | 108 +++++++++++++++++- vnext/Shared/Networking/IHttpResource.h | 1 + vnext/Shared/Networking/WinRTHttpResource.cpp | 27 ++++- 5 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 change/react-native-windows-42dec614-af90-4b29-b8c7-f266415ea240.json diff --git a/change/react-native-windows-42dec614-af90-4b29-b8c7-f266415ea240.json b/change/react-native-windows-42dec614-af90-4b29-b8c7-f266415ea240.json new file mode 100644 index 00000000000..5937254e3f5 --- /dev/null +++ b/change/react-native-windows-42dec614-af90-4b29-b8c7-f266415ea240.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Implement HTTP client timeout", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp index 640383f71e6..40c4fde98da 100644 --- a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp +++ b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp @@ -147,7 +147,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) {}, /*data*/ "text", false, /*useIncrementalUpdates*/ - 1000, /*timeout*/ + 0, /*timeout*/ clientArgs.WithCredentials, /*withCredentials*/ [](int64_t){} /*reactCallback*/ ); @@ -201,7 +201,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) {}, /*data*/ "text", false, /*useIncrementalUpdates*/ - 1000, /*timeout*/ + 0, /*timeout*/ clientArgs.WithCredentials, /*withCredentials*/ [](int64_t) {} /*reactCallback*/ ); @@ -300,7 +300,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) //{} /*bodyData*/, "text", false /*useIncrementalUpdates*/, - 1000 /*timeout*/, + 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {} /*callback*/ ); diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 94a50f65112..7e071b84447 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -78,7 +78,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { {}, /*data*/ "text", false, - 1000 /*timeout*/, + 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); @@ -139,7 +139,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { {}, /*data*/ "text", false, - 1000 /*timeout*/, + 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); //clang-format on @@ -172,7 +172,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { promise.set_value(); }); - resource->SendRequest("GET", "http://nonexistinghost", 0, {}, {}, "text", false, 1000, false, [](int64_t) {}); + resource->SendRequest("GET", "http://nonexistinghost", 0, {}, {}, "text", false, 0, false, [](int64_t) {}); promise.get_future().wait(); @@ -256,7 +256,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { {}, /*data*/ "text", false, - 1000 /*timeout*/, + 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); //clang-format on @@ -339,7 +339,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { {}, /*data*/ "text", false, /*useIncrementalUpdates*/ - 1000 /*timeout*/, + 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); //clang-format on @@ -354,6 +354,104 @@ TEST_CLASS (HttpResourceIntegrationTest) { Assert::AreEqual(static_cast(200), getResponse.StatusCode); Assert::AreEqual({"Redirect Content"}, content); } + + TEST_METHOD(TimeoutSucceeds) { + auto port = s_port; + string url = "http://localhost:" + std::to_string(port); + + promise getPromise; + string error; + int statusCode = 0; + + auto server = std::make_shared(s_port); + server->Callbacks().OnGet = [](const DynamicRequest &) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + + // Hold response to test client timeout + promise timer; + timer.get_future().wait_for(std::chrono::milliseconds(2000)); + + return {std::move(response)}; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + getPromise.set_value(); + }); + resource->SetOnError([&getPromise, &error](int64_t, string &&errorMessage) { + error = std::move(errorMessage); + getPromise.set_value(); + }); + resource->SendRequest( + "GET", + std::move(url), + 0, /*requestId*/ + {}, /*headers*/ + {}, /*data*/ + "text", /*responseType*/ + false, /*useIncrementalUpdates*/ + 6000, /*timeout*/ + false, /*withCredentials*/ + [](int64_t) {} /*callback*/); + + getPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({}, error); + Assert::AreEqual(200, statusCode); + } + + TEST_METHOD(TimeoutFails) { + auto port = s_port; + string url = "http://localhost:" + std::to_string(port); + + promise getPromise; + string error; + int statusCode = 0; + + auto server = std::make_shared(s_port); + server->Callbacks().OnGet = [](const DynamicRequest &) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + + // Hold response to test client timeout + promise timer; + timer.get_future().wait_for(std::chrono::milliseconds(4000)); + + return {std::move(response)}; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + getPromise.set_value(); + }); + resource->SetOnError([&getPromise, &error](int64_t, string &&errorMessage) { + error = std::move(errorMessage); + getPromise.set_value(); + }); + resource->SendRequest( + "GET", + std::move(url), + 0, /*requestId*/ + {}, /*headers*/ + {}, /*data*/ + "text", /*responseType*/ + false, /*useIncrementalUpdates*/ + 2000, /*timeout*/ + false, /*withCredentials*/ + [](int64_t) {} /*callback*/); + + getPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({"[0x800705b4] This operation returned because the timeout period expired."}, error); + Assert::AreEqual(0, statusCode); + } }; /*static*/ uint16_t HttpResourceIntegrationTest::s_port = 4444; diff --git a/vnext/Shared/Networking/IHttpResource.h b/vnext/Shared/Networking/IHttpResource.h index 7a8434e9f6f..173c79cda6e 100644 --- a/vnext/Shared/Networking/IHttpResource.h +++ b/vnext/Shared/Networking/IHttpResource.h @@ -71,6 +71,7 @@ struct IHttpResource { /// /// /// Request timeout in miliseconds. + /// Note: A value of 0 means no timeout. The resource will await the response indefinitely. /// /// /// Allow including credentials in request. diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index a496186d769..49b1d0e3875 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -297,7 +297,32 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque auto sendRequestOp = self->m_client.SendRequestAsync(coRequest); self->TrackResponse(coReqArgs->RequestId, sendRequestOp); - co_await lessthrow_await_adapter{sendRequestOp}; + if (coReqArgs->Timeout > 0) { + // See https://devblogs.microsoft.com/oldnewthing/20220415-00/?p=106486 + auto timedOut = std::make_shared(false); + auto sendRequestTimeout = [](auto timedOut, auto milliseconds) -> ResponseOperation { + // Convert milliseconds to "ticks" (10^-7 seconds) + co_await winrt::resume_after(winrt::Windows::Foundation::TimeSpan{milliseconds * 10000}); + *timedOut = true; + co_return nullptr; + }(timedOut, coReqArgs->Timeout); + + co_await lessthrow_await_adapter{winrt::when_any(sendRequestOp, sendRequestTimeout)}; + + // Cancel either still unfinished coroutine. + sendRequestTimeout.Cancel(); + sendRequestOp.Cancel(); + + if (*timedOut) { + if (self->m_onError) { + self->m_onError(coReqArgs->RequestId, Utilities::HResultToString(HRESULT_FROM_WIN32(ERROR_TIMEOUT))); + } + co_return self->UntrackResponse(coReqArgs->RequestId); + } + } else { + co_await lessthrow_await_adapter{sendRequestOp}; + } + auto result = sendRequestOp.ErrorCode(); if (result < 0) { if (self->m_onError) { From 351305fdab70f70b4690b20e67bd8fefb3b5a83d Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 15:58:19 -0700 Subject: [PATCH 08/23] Remove change files --- ...ative-windows-42dec614-af90-4b29-b8c7-f266415ea240.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 change/react-native-windows-42dec614-af90-4b29-b8c7-f266415ea240.json diff --git a/change/react-native-windows-42dec614-af90-4b29-b8c7-f266415ea240.json b/change/react-native-windows-42dec614-af90-4b29-b8c7-f266415ea240.json deleted file mode 100644 index 5937254e3f5..00000000000 --- a/change/react-native-windows-42dec614-af90-4b29-b8c7-f266415ea240.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "prerelease", - "comment": "Implement HTTP client timeout", - "packageName": "react-native-windows", - "email": "julio.rocha@microsoft.com", - "dependentChangeType": "patch" -} From 8f7fbbc240f92d7aee7678c1c322fbf3d56b0e12 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Fri, 22 Jul 2022 20:06:55 -0400 Subject: [PATCH 09/23] Use uint8_t const in IBlobPersistor.h (#10276) * Use uint8_t const in IBlobPersistor.h Some versions of clang will not compile when the array_view data for CryptographicBuffer::CreateFromByteArray is not `uint8_t const`. This change switches the callsites to use uint8_t const where needed. * Change files * Fix Blob test comparison Co-authored-by: Julio C. Rocha --- ...-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json | 7 + vnext/Shared/Modules/BlobModule.cpp | 8 +- vnext/Shared/Modules/BlobModule.h | 2 +- vnext/Shared/Modules/FileReaderModule.cpp | 4 +- vnext/Shared/Modules/IBlobPersistor.h | 2 +- vnext/Shared/Networking/WinRTHttpResource.cpp | 2 +- vnext/src/IntegrationTests/BlobTest.js | 233 +++++++++--------- 7 files changed, 132 insertions(+), 126 deletions(-) create mode 100644 change/react-native-windows-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json diff --git a/change/react-native-windows-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json b/change/react-native-windows-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json new file mode 100644 index 00000000000..0d5014c93c4 --- /dev/null +++ b/change/react-native-windows-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Use uint8_t const in IBlobPersistor.h", + "packageName": "react-native-windows", + "email": "erozell@outlook.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Shared/Modules/BlobModule.cpp b/vnext/Shared/Modules/BlobModule.cpp index b03b2e44269..867e45c0193 100644 --- a/vnext/Shared/Modules/BlobModule.cpp +++ b/vnext/Shared/Modules/BlobModule.cpp @@ -139,7 +139,7 @@ vector BlobModule::getMethods() { auto size = blob[sizeKey].getInt(); auto socketID = jsArgAsInt(args, 1); - winrt::array_view data; + winrt::array_view data; try { data = persistor->ResolveMessage(std::move(blobId), offset, size); } catch (const std::exception &e) { @@ -169,7 +169,7 @@ vector BlobModule::getMethods() { auto type = part[typeKey].asString(); if (blobKey == type) { auto blob = part[dataKey]; - winrt::array_view bufferPart; + winrt::array_view bufferPart; try { bufferPart = persistor->ResolveMessage( blob[blobIdKey].asString(), blob[offsetKey].asInt(), blob[sizeKey].asInt()); @@ -216,7 +216,7 @@ vector BlobModule::getMethods() { #pragma region IBlobPersistor -winrt::array_view MemoryBlobPersistor::ResolveMessage(string &&blobId, int64_t offset, int64_t size) { +winrt::array_view MemoryBlobPersistor::ResolveMessage(string &&blobId, int64_t offset, int64_t size) { if (size < 1) return {}; @@ -233,7 +233,7 @@ winrt::array_view MemoryBlobPersistor::ResolveMessage(string &&blobId, 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); + return winrt::array_view(bytes.data() + offset, bytes.data() + endBound); } void MemoryBlobPersistor::RemoveMessage(string &&blobId) noexcept { diff --git a/vnext/Shared/Modules/BlobModule.h b/vnext/Shared/Modules/BlobModule.h index 06e9b0b4695..76c5d46e5e7 100644 --- a/vnext/Shared/Modules/BlobModule.h +++ b/vnext/Shared/Modules/BlobModule.h @@ -30,7 +30,7 @@ class MemoryBlobPersistor final : public IBlobPersistor { public: #pragma region IBlobPersistor - winrt::array_view ResolveMessage(std::string &&blobId, int64_t offset, int64_t size) override; + winrt::array_view ResolveMessage(std::string &&blobId, int64_t offset, int64_t size) override; void RemoveMessage(std::string &&blobId) noexcept override; diff --git a/vnext/Shared/Modules/FileReaderModule.cpp b/vnext/Shared/Modules/FileReaderModule.cpp index a23328c0e15..9f7919e31fc 100644 --- a/vnext/Shared/Modules/FileReaderModule.cpp +++ b/vnext/Shared/Modules/FileReaderModule.cpp @@ -71,7 +71,7 @@ std::vector FileReaderModule::getMethods() { auto offset = blob["offset"].asInt(); auto size = blob["size"].asInt(); - winrt::array_view bytes; + winrt::array_view bytes; try { bytes = blobPersistor->ResolveMessage(std::move(blobId), offset, size); } catch (const std::exception &e) { @@ -116,7 +116,7 @@ std::vector FileReaderModule::getMethods() { auto offset = blob["offset"].asInt(); auto size = blob["size"].asInt(); - winrt::array_view bytes; + winrt::array_view bytes; try { bytes = blobPersistor->ResolveMessage(std::move(blobId), offset, size); } catch (const std::exception &e) { diff --git a/vnext/Shared/Modules/IBlobPersistor.h b/vnext/Shared/Modules/IBlobPersistor.h index b1035fc461e..e4aaf5017e0 100644 --- a/vnext/Shared/Modules/IBlobPersistor.h +++ b/vnext/Shared/Modules/IBlobPersistor.h @@ -18,7 +18,7 @@ struct IBlobPersistor { /// When an entry for blobId cannot be found. /// /// - virtual winrt::array_view ResolveMessage(std::string &&blobId, int64_t offset, int64_t size) = 0; + virtual winrt::array_view ResolveMessage(std::string &&blobId, int64_t offset, int64_t size) = 0; virtual void RemoveMessage(std::string &&blobId) noexcept = 0; diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index 49b1d0e3875..ccfcf5a7369 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -249,7 +249,7 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque for (auto &byte : bytes) { byteVector.push_back(static_cast(byte.asInt())); } - auto view = winrt::array_view{byteVector}; + auto view = winrt::array_view{byteVector}; auto buffer = CryptographicBuffer::CreateFromByteArray(view); content = HttpBufferContent{std::move(buffer)}; } else if (!data["string"].empty()) { diff --git a/vnext/src/IntegrationTests/BlobTest.js b/vnext/src/IntegrationTests/BlobTest.js index 3518b5276eb..cc04855ca51 100644 --- a/vnext/src/IntegrationTests/BlobTest.js +++ b/vnext/src/IntegrationTests/BlobTest.js @@ -16,7 +16,7 @@ const {TestModule} = ReactNative.NativeModules; type State = { statusCode: number, xhr: XMLHttpRequest, - expected: String, + expected: string, }; class BlobTest extends React.Component<{...}, State> { @@ -24,123 +24,122 @@ class BlobTest extends React.Component<{...}, State> { statusCode: 0, xhr: new XMLHttpRequest(), // https://www.facebook.com/favicon.ico - expected: new String( + expected: 'data:application/octet-stream;base64,' + - 'AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAA' + - 'AAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOFl' + - 'BiviZgKP4WYB1f//////////4WUA1eJmAI/hawYrAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAA/4ArBuNpA5TkawP942kC/+NpAv///////////+NpAv/jaQL/5GoD/eNp' + - 'A5T/gCsGAAAAAAAAAAAAAAAA/4ArBuVvBL3lbgT/5W4E/+VuBP/lbgT/////////' + - '///lbgT/5W4E/+VuBP/lbgT/5W8Evf+AKwYAAAAAAAAAAOlzBZTncwX/53MF/+dz' + - 'Bf/ncwX/53MF////////////53MF/+dzBf/ncwb/53MF/+dzBv/pcweUAAAAAO19' + - 'DCvpeAf96HcH/+l4B//odwf/6XgH/+l4B////////////+h3B//odwf/6XgH/+h3' + - 'B//peAf/6nkH/e19DCvrfQmP630J/+t9Cf/rfAn/630J/+t8Cf/rfAn/////////' + - '///rfQn/630J/+p8CP/rfQn/6nwI/+p8CP/rfQuP7YEL1e2BCv/tgQr/7IEK/+2B' + - 'Cv/////////////////////////////////uiRj/7IEK/+2CCv/tggr/7YIM1e6G' + - 'DfPvhgz/74YM/++HDP/vhgz/////////////////////////////////8Zw4/++G' + - 'DP/uhgz/7oYM/+6GDPPwiw7z8IsN//CLDf/wiw3/8IsN//CLDf/wiw3/////////' + - '///wig3/8IoN//CLDf/wig3/8IsN//CLDf/xjA/z85EQ1fOQD//zkA//85AP//OQ' + - 'D//zkA//85AP////////////8o8P//KPD//ykA//8o8P//KQD//ykA//85EQ1fSU' + - 'EI/1lRH/9ZUR//SUEP/1lRH/9JQQ//SUEP/+9uz///////jDev/0mRz/9ZUR//SV' + - 'Ef/1lRH/9ZUR//SUEI/5mhgr95kS/faZEv/2mRL/9pkS//aZEv/2mRL//Nqo////' + - '//////////////rLhv/3mhL/9pkS//eZEv35mhgrAAAAAPifFZT4nhT/+Z8U//ie' + - 'FP/5nxT/+Z8U//ikI//83a3//vjw//78+f/7yX3/+J4T//ieFP/4nxWUAAAAAAAA' + - 'AAD/qisG+6MWvfqjFf/6oxX/+qMV//qjFf/6oxX/+qMV//qjFf/6oxX/+qIV//qj' + - 'Ff/7oxa9/6orBgAAAAAAAAAAAAAAAP+qKwb9qRiU/agW/fyoF//8qBf//agX//yo' + - 'F//9qBf//agX//2oF/39qRiU/6orBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+y' + - 'Hiv/rRmP/6wZ1f+tGPP/rBjz/64Z1f+vGY//sh4rAAAAAAAAAAAAAAAAAAAAAPAP' + - 'AADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB' + - 'AACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+A' + - 'AAbiZQRH4GMAlf//////////////////////////4GQAv+BjAJXiZQBH/4AABgAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOpqCxjiZgKW4WYB8eJmAf/hZQH/////////' + - '///////////////////hZgH/4mYB/+FmAf/iZwHx4mYClupqCxgAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9t' + - 'JAfkagSC42kC9ONoAv/jaAL/4mgC/+NoAv///////////////////////////+Jo' + - 'Af/iaAL/4mgB/+JoAv/iaAL/42kC9ORqBIL/bSQHAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADqagsY5GoEx+NqA//jagP/42oD/+Nq' + - 'A//kawP/42oD////////////////////////////42oD/+RrA//jagP/5GsD/+Rr' + - 'A//kawP/5GsD/+RsBMfqdQsYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAA6HEGLeVuBOPlbQT/5GwD/+VtBP/kbAP/5GwD/+VtBP/kbAP/////////' + - '///////////////////kbAP/5G0D/+RsA//kbQP/5G0D/+RtA//kbQP/5G0D/+Ru' + - 'A+PocQYtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOp1CxjmcAbj5W8E/+Vv' + - 'BP/lbwT/5W8E/+VvBP/lbwT/5W8E/+VvBP///////////////////////////+Vv' + - 'BP/mcAX/5W8E/+ZwBf/mcAX/5W8E/+ZwBf/lbwT/5W8E/+ZwBuPqdQsYAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAD/kiQH53IFx+ZyBf/mcQX/5nEF/+ZxBf/mcQX/5nEF/+Zx' + - 'Bf/ncgX/5nEF////////////////////////////53IG/+ZxBf/ncgb/5nEF/+Zx' + - 'Bf/mcgX/5nEF/+ZyBf/mcgX/5nEF/+dyBcf/kiQHAAAAAAAAAAAAAAAAAAAAAOd2' + - 'CILodAb/53QG/+h0Bv/odAb/6HQG/+h0Bv/odAb/6HQG/+d0Bv/odAb/////////' + - '///////////////////ndAX/53QG/+d0Bf/ndAb/53QG/+h1Bv/ndAb/6HUG/+h1' + - 'Bv/ndAX/6HUG/+d2BoIAAAAAAAAAAAAAAADqgAsY6HYG9Oh2B//odgb/6HYH/+h2' + - 'B//odgb/6HYH/+h2Bv/odgb/6HYH/+h2Bv///////////////////////////+l3' + - 'B//odgb/6XcH/+h2Bv/odgb/6HYH/+h2Bv/odgf/6HYH/+h2Bv/odgf/6HYG9OqA' + - 'CxgAAAAAAAAAAOt6CZbpeQj/6nkI/+l5CP/qeQj/6nkI/+l5B//qeQj/6XkH/+l5' + - 'B//peQf/6XkH////////////////////////////6XkI/+l5B//peQj/6XkH/+l5' + - 'B//peQj/6XkH/+l5CP/peQj/6XkH/+l5CP/peQf/63oJlgAAAAD/gCsG7H0K8et8' + - 'Cf/qewj/63wJ/+p7CP/qewj/6nsI/+p7CP/qewj/6nsI/+t8Cf/qewj/////////' + - '///////////////////qewj/6nwJ/+p7CP/qfAn/6nwJ/+p7CP/qfAn/6nsI/+p7' + - 'CP/rfAn/6nsI/+t8Cf/sfQrx/4ArBu2BC0frfQn/630J/+t+Cf/rfQn/634J/+t+' + - 'Cf/rfgn/634J////////////////////////////////////////////////////' + - '///////////////////zs27/634J/+x+Cf/rfgn/634J/+t+Cf/rfgn/634J/+t+' + - 'Cf/tgQtH7IAKleyACv/sgAr/7IAK/+yACv/sgAr/7IAK/+yACv/sgAr/////////' + - '//////////////////////////////////////////////////////////////XC' + - 'iv/sgAr/7IAK/+yACv/sgAr/7IAJ/+yACv/sgAn/7IAJ/+yACpXugwu/7YML/+2D' + - 'C//tggr/7YML/+2CCv/tggr/7YIK/+2CCv//////////////////////////////' + - '////////////////////////////////////////+NKn/+2DC//tggr/7YML/+2D' + - 'C//tgwv/7YML/+2DC//tgwv/7oMLv++GDNnuhQv/7oUL/+6FC//uhQv/7oUL/+6F' + - 'C//vhQz/7oUL////////////////////////////////////////////////////' + - '///////////////////64cT/7oUL/+6FC//uhQv/7oUL/+6EC//uhQv/7oQL/+6E' + - 'C//vhgzZ74gO8++IDP/viAz/74cM/++IDP/vhwz/74cM/++HDP/vhwz/////////' + - '//////////////////////////////////////////////////////////////3w' + - '4f/viA3/74cM/++IDf/viA3/74cM/++IDf/vhwz/74cM/++HDfPwiw7z8IoN//CK' + - 'Df/wig3/8IoN//CKDf/wig3/8IkN//CKDf/wiQ3/8IkN//CKDf/wiQ3/////////' + - '///////////////////wiQ3/8IoN//CJDf/wig3/8IoN//CJDf/wig3/8IkN//CJ' + - 'Df/wiQ3/8IkN//CJDf/wiQ3/8IsO8/KNDtnxjA7/8YwO//GMDf/xjA7/8YwN//GM' + - 'Df/xjA3/8YwN//GMDf/xjA3/8YwO//GMDf////////////////////////////GM' + - 'Dv/xjA7/8YwO//GMDv/xjA7/8YwO//GMDv/xjA7/8YwO//GMDv/xjA7/8YwO//GM' + - 'Dv/yjQ7Z8o8Pv/KPD//yjw//8o8P//KPD//yjw//8o8P//KPD//yjw//8o8P//KP' + - 'D//yjg7/8o8P////////////////////////////8Y4O//KODv/xjg7/8o4O//KO' + - 'Dv/yjg7/8o4O//KODv/yjg7/8o8P//KODv/yjw//8o8P//OQEL/zkQ+V85EP//OR' + - 'D//zkQ//85EP//ORD//zkQ//85EP//ORD//zkQ//85EP//OREP/zkQ///vr0////' + - '///////////////////0myb/85EQ//ORD//zkRD/85EQ//ORD//zkRD/85EP//OR' + - 'D//zkQ//85EP//ORD//zkQ//85EPlfSXEkf0kxD/9JMQ//SUEP/0kxD/9JQQ//SU' + - 'EP/zkxD/9JQQ//OTEP/zkxD/9JQQ//OTEP/86tD///////////////////////rV' + - 'ov/1nSb/85MQ//STEP/0kxD/9JMQ//STEP/0kxD/9JMQ//SUEP/0kxD/9JQQ//SU' + - 'EP/0kxJH/6orBvWWEvH1lhH/9ZYR//WWEf/1lhH/9ZYR//WWEf/1lhH/9ZYR//WW' + - 'Ef/1lhH/9ZYR//vZq///////////////////////////////////////////////' + - '///1lhH/9ZYR//WWEf/1lhH/9ZYR//WWEf/1lhH/9ZYS8f+qKwYAAAAA95kTlvaY' + - 'Ev/2mBH/9pgS//aYEf/2mBH/9ZgR//aYEf/1mBH/9ZgR//aYEv/1mBH/+LFN////' + - '//////////////////////////////////////////////aYEv/1mBH/9pgS//aY' + - 'Ev/1mBH/9pgS//WYEf/3mRGWAAAAAAAAAAD/nxUY+JwU9PebE//3mxP/95sT//eb' + - 'E//3mxP/95sT//ebE//3mxP/95oS//ebE//3mhL//OK7////////////////////' + - '////////////////////////95sT//ebE//3mxP/95sT//aaEv/3mxP/95sT9P+f' + - 'FRgAAAAAAAAAAAAAAAD5nxSC+J0T//idE//4nRP/+J0T//ecE//4nRP/95wT//ec' + - 'E//4nRP/95wT//idE//4pSf//efF////////////////////////////////////' + - '///4nRP/950T//idE//4nRP/+J0T//idE//5nxSCAAAAAAAAAAAAAAAAAAAAAP+2' + - 'JAf6oBXH+aAU//mgFP/5oBT/+J8U//mgFP/4nxT/+J8U//mfFP/4nxT/+Z8U//mf' + - 'FP/5oRf/+86H//7w2v/+/Pj//v36//758f/+8+P//evQ//mgFP/5nxT/+aAU//mg' + - 'FP/4nxT/+qAVx/+2JAcAAAAAAAAAAAAAAAAAAAAAAAAAAP+qFRj7oxXj+aEU//mh' + - 'FP/6ohX/+aEU//qiFf/6ohX/+qIV//qiFf/6ohX/+qIV//mhFP/6ohX/+aEU//mh' + - 'FP/6ohX/+aEU//qiFf/6ohX/+aEU//qiFf/5oRT/+aEU//ujFeP/qhUYAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+mFi78pBXj+6QV//ukFv/7pBX/+6QW//uk' + - 'Fv/6pBX/+6QW//qkFf/6pBX/+6QW//qkFf/7pBb/+6QW//ulFv/7pBb/+6UW//ul' + - 'Fv/7pBX/+6UW//ukFf/8pBXj/6QXLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAP+qIBj8qBfH/KcW//ynF//8pxb//KcW//ynFv/8pxb//KcW//yn' + - 'Fv/7phb//KcW//umFv/7phb/+6YW//umFv/7phb/+6YW//ynFv/7phb//KgXx/+q' + - 'IBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+2' + - 'JAf9qxiC/akY9PypF//8qRf//KgX//ypF//8qBf//KgX//2pF//8qBf//akX//2p' + - 'F//9qRf//akX//2pF//9qRf//qkY9P2rGIL/tiQHAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/tSAY/60alv+s' + - 'GPH+rBj//qwY//6sGP/+rBj//asY//6sGP/9qxj//asY//2rF//9qxj//qsX8f2s' + - 'GJb/tSAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9UrBv+wGUf/rxqV/68Zv/+v' + - 'Gtn/rhnz/64Z8/+vGtn/rxm//68alf+wGUf/1SsGAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/gAAB/wAAAP4AAAB8AA' + - 'AAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAACAAAABgAAAAcAAAAPAAAAD4AAAB/AAAA/4AAAf/AAAP/8A' + - 'AP//wAP/', - ), + 'AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAA' + + 'AAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOFl' + + 'BiviZgKP4WYB1f//////////4WUA1eJmAI/hawYrAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAA/4ArBuNpA5TkawP942kC/+NpAv///////////+NpAv/jaQL/5GoD/eNp' + + 'A5T/gCsGAAAAAAAAAAAAAAAA/4ArBuVvBL3lbgT/5W4E/+VuBP/lbgT/////////' + + '///lbgT/5W4E/+VuBP/lbgT/5W8Evf+AKwYAAAAAAAAAAOlzBZTncwX/53MF/+dz' + + 'Bf/ncwX/53MF////////////53MF/+dzBf/ncwb/53MF/+dzBv/pcweUAAAAAO19' + + 'DCvpeAf96HcH/+l4B//odwf/6XgH/+l4B////////////+h3B//odwf/6XgH/+h3' + + 'B//peAf/6nkH/e19DCvrfQmP630J/+t9Cf/rfAn/630J/+t8Cf/rfAn/////////' + + '///rfQn/630J/+p8CP/rfQn/6nwI/+p8CP/rfQuP7YEL1e2BCv/tgQr/7IEK/+2B' + + 'Cv/////////////////////////////////uiRj/7IEK/+2CCv/tggr/7YIM1e6G' + + 'DfPvhgz/74YM/++HDP/vhgz/////////////////////////////////8Zw4/++G' + + 'DP/uhgz/7oYM/+6GDPPwiw7z8IsN//CLDf/wiw3/8IsN//CLDf/wiw3/////////' + + '///wig3/8IoN//CLDf/wig3/8IsN//CLDf/xjA/z85EQ1fOQD//zkA//85AP//OQ' + + 'D//zkA//85AP////////////8o8P//KPD//ykA//8o8P//KQD//ykA//85EQ1fSU' + + 'EI/1lRH/9ZUR//SUEP/1lRH/9JQQ//SUEP/+9uz///////jDev/0mRz/9ZUR//SV' + + 'Ef/1lRH/9ZUR//SUEI/5mhgr95kS/faZEv/2mRL/9pkS//aZEv/2mRL//Nqo////' + + '//////////////rLhv/3mhL/9pkS//eZEv35mhgrAAAAAPifFZT4nhT/+Z8U//ie' + + 'FP/5nxT/+Z8U//ikI//83a3//vjw//78+f/7yX3/+J4T//ieFP/4nxWUAAAAAAAA' + + 'AAD/qisG+6MWvfqjFf/6oxX/+qMV//qjFf/6oxX/+qMV//qjFf/6oxX/+qIV//qj' + + 'Ff/7oxa9/6orBgAAAAAAAAAAAAAAAP+qKwb9qRiU/agW/fyoF//8qBf//agX//yo' + + 'F//9qBf//agX//2oF/39qRiU/6orBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+y' + + 'Hiv/rRmP/6wZ1f+tGPP/rBjz/64Z1f+vGY//sh4rAAAAAAAAAAAAAAAAAAAAAPAP' + + 'AADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB' + + 'AACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+A' + + 'AAbiZQRH4GMAlf//////////////////////////4GQAv+BjAJXiZQBH/4AABgAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOpqCxjiZgKW4WYB8eJmAf/hZQH/////////' + + '///////////////////hZgH/4mYB/+FmAf/iZwHx4mYClupqCxgAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9t' + + 'JAfkagSC42kC9ONoAv/jaAL/4mgC/+NoAv///////////////////////////+Jo' + + 'Af/iaAL/4mgB/+JoAv/iaAL/42kC9ORqBIL/bSQHAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADqagsY5GoEx+NqA//jagP/42oD/+Nq' + + 'A//kawP/42oD////////////////////////////42oD/+RrA//jagP/5GsD/+Rr' + + 'A//kawP/5GsD/+RsBMfqdQsYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAA6HEGLeVuBOPlbQT/5GwD/+VtBP/kbAP/5GwD/+VtBP/kbAP/////////' + + '///////////////////kbAP/5G0D/+RsA//kbQP/5G0D/+RtA//kbQP/5G0D/+Ru' + + 'A+PocQYtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOp1CxjmcAbj5W8E/+Vv' + + 'BP/lbwT/5W8E/+VvBP/lbwT/5W8E/+VvBP///////////////////////////+Vv' + + 'BP/mcAX/5W8E/+ZwBf/mcAX/5W8E/+ZwBf/lbwT/5W8E/+ZwBuPqdQsYAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAD/kiQH53IFx+ZyBf/mcQX/5nEF/+ZxBf/mcQX/5nEF/+Zx' + + 'Bf/ncgX/5nEF////////////////////////////53IG/+ZxBf/ncgb/5nEF/+Zx' + + 'Bf/mcgX/5nEF/+ZyBf/mcgX/5nEF/+dyBcf/kiQHAAAAAAAAAAAAAAAAAAAAAOd2' + + 'CILodAb/53QG/+h0Bv/odAb/6HQG/+h0Bv/odAb/6HQG/+d0Bv/odAb/////////' + + '///////////////////ndAX/53QG/+d0Bf/ndAb/53QG/+h1Bv/ndAb/6HUG/+h1' + + 'Bv/ndAX/6HUG/+d2BoIAAAAAAAAAAAAAAADqgAsY6HYG9Oh2B//odgb/6HYH/+h2' + + 'B//odgb/6HYH/+h2Bv/odgb/6HYH/+h2Bv///////////////////////////+l3' + + 'B//odgb/6XcH/+h2Bv/odgb/6HYH/+h2Bv/odgf/6HYH/+h2Bv/odgf/6HYG9OqA' + + 'CxgAAAAAAAAAAOt6CZbpeQj/6nkI/+l5CP/qeQj/6nkI/+l5B//qeQj/6XkH/+l5' + + 'B//peQf/6XkH////////////////////////////6XkI/+l5B//peQj/6XkH/+l5' + + 'B//peQj/6XkH/+l5CP/peQj/6XkH/+l5CP/peQf/63oJlgAAAAD/gCsG7H0K8et8' + + 'Cf/qewj/63wJ/+p7CP/qewj/6nsI/+p7CP/qewj/6nsI/+t8Cf/qewj/////////' + + '///////////////////qewj/6nwJ/+p7CP/qfAn/6nwJ/+p7CP/qfAn/6nsI/+p7' + + 'CP/rfAn/6nsI/+t8Cf/sfQrx/4ArBu2BC0frfQn/630J/+t+Cf/rfQn/634J/+t+' + + 'Cf/rfgn/634J////////////////////////////////////////////////////' + + '///////////////////zs27/634J/+x+Cf/rfgn/634J/+t+Cf/rfgn/634J/+t+' + + 'Cf/tgQtH7IAKleyACv/sgAr/7IAK/+yACv/sgAr/7IAK/+yACv/sgAr/////////' + + '//////////////////////////////////////////////////////////////XC' + + 'iv/sgAr/7IAK/+yACv/sgAr/7IAJ/+yACv/sgAn/7IAJ/+yACpXugwu/7YML/+2D' + + 'C//tggr/7YML/+2CCv/tggr/7YIK/+2CCv//////////////////////////////' + + '////////////////////////////////////////+NKn/+2DC//tggr/7YML/+2D' + + 'C//tgwv/7YML/+2DC//tgwv/7oMLv++GDNnuhQv/7oUL/+6FC//uhQv/7oUL/+6F' + + 'C//vhQz/7oUL////////////////////////////////////////////////////' + + '///////////////////64cT/7oUL/+6FC//uhQv/7oUL/+6EC//uhQv/7oQL/+6E' + + 'C//vhgzZ74gO8++IDP/viAz/74cM/++IDP/vhwz/74cM/++HDP/vhwz/////////' + + '//////////////////////////////////////////////////////////////3w' + + '4f/viA3/74cM/++IDf/viA3/74cM/++IDf/vhwz/74cM/++HDfPwiw7z8IoN//CK' + + 'Df/wig3/8IoN//CKDf/wig3/8IkN//CKDf/wiQ3/8IkN//CKDf/wiQ3/////////' + + '///////////////////wiQ3/8IoN//CJDf/wig3/8IoN//CJDf/wig3/8IkN//CJ' + + 'Df/wiQ3/8IkN//CJDf/wiQ3/8IsO8/KNDtnxjA7/8YwO//GMDf/xjA7/8YwN//GM' + + 'Df/xjA3/8YwN//GMDf/xjA3/8YwO//GMDf////////////////////////////GM' + + 'Dv/xjA7/8YwO//GMDv/xjA7/8YwO//GMDv/xjA7/8YwO//GMDv/xjA7/8YwO//GM' + + 'Dv/yjQ7Z8o8Pv/KPD//yjw//8o8P//KPD//yjw//8o8P//KPD//yjw//8o8P//KP' + + 'D//yjg7/8o8P////////////////////////////8Y4O//KODv/xjg7/8o4O//KO' + + 'Dv/yjg7/8o4O//KODv/yjg7/8o8P//KODv/yjw//8o8P//OQEL/zkQ+V85EP//OR' + + 'D//zkQ//85EP//ORD//zkQ//85EP//ORD//zkQ//85EP//OREP/zkQ///vr0////' + + '///////////////////0myb/85EQ//ORD//zkRD/85EQ//ORD//zkRD/85EP//OR' + + 'D//zkQ//85EP//ORD//zkQ//85EPlfSXEkf0kxD/9JMQ//SUEP/0kxD/9JQQ//SU' + + 'EP/zkxD/9JQQ//OTEP/zkxD/9JQQ//OTEP/86tD///////////////////////rV' + + 'ov/1nSb/85MQ//STEP/0kxD/9JMQ//STEP/0kxD/9JMQ//SUEP/0kxD/9JQQ//SU' + + 'EP/0kxJH/6orBvWWEvH1lhH/9ZYR//WWEf/1lhH/9ZYR//WWEf/1lhH/9ZYR//WW' + + 'Ef/1lhH/9ZYR//vZq///////////////////////////////////////////////' + + '///1lhH/9ZYR//WWEf/1lhH/9ZYR//WWEf/1lhH/9ZYS8f+qKwYAAAAA95kTlvaY' + + 'Ev/2mBH/9pgS//aYEf/2mBH/9ZgR//aYEf/1mBH/9ZgR//aYEv/1mBH/+LFN////' + + '//////////////////////////////////////////////aYEv/1mBH/9pgS//aY' + + 'Ev/1mBH/9pgS//WYEf/3mRGWAAAAAAAAAAD/nxUY+JwU9PebE//3mxP/95sT//eb' + + 'E//3mxP/95sT//ebE//3mxP/95oS//ebE//3mhL//OK7////////////////////' + + '////////////////////////95sT//ebE//3mxP/95sT//aaEv/3mxP/95sT9P+f' + + 'FRgAAAAAAAAAAAAAAAD5nxSC+J0T//idE//4nRP/+J0T//ecE//4nRP/95wT//ec' + + 'E//4nRP/95wT//idE//4pSf//efF////////////////////////////////////' + + '///4nRP/950T//idE//4nRP/+J0T//idE//5nxSCAAAAAAAAAAAAAAAAAAAAAP+2' + + 'JAf6oBXH+aAU//mgFP/5oBT/+J8U//mgFP/4nxT/+J8U//mfFP/4nxT/+Z8U//mf' + + 'FP/5oRf/+86H//7w2v/+/Pj//v36//758f/+8+P//evQ//mgFP/5nxT/+aAU//mg' + + 'FP/4nxT/+qAVx/+2JAcAAAAAAAAAAAAAAAAAAAAAAAAAAP+qFRj7oxXj+aEU//mh' + + 'FP/6ohX/+aEU//qiFf/6ohX/+qIV//qiFf/6ohX/+qIV//mhFP/6ohX/+aEU//mh' + + 'FP/6ohX/+aEU//qiFf/6ohX/+aEU//qiFf/5oRT/+aEU//ujFeP/qhUYAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+mFi78pBXj+6QV//ukFv/7pBX/+6QW//uk' + + 'Fv/6pBX/+6QW//qkFf/6pBX/+6QW//qkFf/7pBb/+6QW//ulFv/7pBb/+6UW//ul' + + 'Fv/7pBX/+6UW//ukFf/8pBXj/6QXLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAP+qIBj8qBfH/KcW//ynF//8pxb//KcW//ynFv/8pxb//KcW//yn' + + 'Fv/7phb//KcW//umFv/7phb/+6YW//umFv/7phb/+6YW//ynFv/7phb//KgXx/+q' + + 'IBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+2' + + 'JAf9qxiC/akY9PypF//8qRf//KgX//ypF//8qBf//KgX//2pF//8qBf//akX//2p' + + 'F//9qRf//akX//2pF//9qRf//qkY9P2rGIL/tiQHAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/tSAY/60alv+s' + + 'GPH+rBj//qwY//6sGP/+rBj//asY//6sGP/9qxj//asY//2rF//9qxj//qsX8f2s' + + 'GJb/tSAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9UrBv+wGUf/rxqV/68Zv/+v' + + 'Gtn/rhnz/64Z8/+vGtn/rxm//68alf+wGUf/1SsGAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/gAAB/wAAAP4AAAB8AA' + + 'AAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAACAAAABgAAAAcAAAAPAAAAD4AAAB/AAAA/4AAAf/AAAP/8A' + + 'AP//wAP/', }; _get = () => { From 55c280edf2bd2c0eef9d9f99cddb323a36f8edd1 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 15:58:43 -0700 Subject: [PATCH 10/23] Remove change files --- ...ative-windows-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 change/react-native-windows-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json diff --git a/change/react-native-windows-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json b/change/react-native-windows-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json deleted file mode 100644 index 0d5014c93c4..00000000000 --- a/change/react-native-windows-195fdcc2-1b14-49aa-a0eb-06763be2eed8.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "prerelease", - "comment": "Use uint8_t const in IBlobPersistor.h", - "packageName": "react-native-windows", - "email": "erozell@outlook.com", - "dependentChangeType": "patch" -} From 3c03c87b32ac07c33bb5f7768fbd0f9b3192e860 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Mon, 15 Aug 2022 11:51:53 -0400 Subject: [PATCH 11/23] Adds missing headers for HttpRequestHeaderCollection (#10277) * Adds missing headers for HttpRequestHeaderCollection These headers are needed in case alternative PCH are used to compile (e.g., with clang BUCK). * Change files --- ...ative-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json | 7 +++++++ vnext/Shared/Networking/OriginPolicyHttpFilter.cpp | 1 + vnext/Shared/Networking/WinRTHttpResource.cpp | 1 + 3 files changed, 9 insertions(+) create mode 100644 change/react-native-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json diff --git a/change/react-native-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json b/change/react-native-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json new file mode 100644 index 00000000000..526d80aee48 --- /dev/null +++ b/change/react-native-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Adds missing headers for HttpRequestHeaderCollection", + "packageName": "react-native-windows", + "email": "erozell@outlook.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Shared/Networking/OriginPolicyHttpFilter.cpp b/vnext/Shared/Networking/OriginPolicyHttpFilter.cpp index b6430a119a7..f98f07e67aa 100644 --- a/vnext/Shared/Networking/OriginPolicyHttpFilter.cpp +++ b/vnext/Shared/Networking/OriginPolicyHttpFilter.cpp @@ -12,6 +12,7 @@ #include // Windows API +#include #include // Standard Library diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index ccfcf5a7369..0460e41d421 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -14,6 +14,7 @@ #include // Windows API +#include #include #include #include From 62b715127264497e56e5592a7cfefd8aa4b0b9f3 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 15:59:06 -0700 Subject: [PATCH 12/23] Remove change files --- ...ative-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 change/react-native-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json diff --git a/change/react-native-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json b/change/react-native-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json deleted file mode 100644 index 526d80aee48..00000000000 --- a/change/react-native-windows-ed8de10f-3224-4e5d-8624-ff9f24b9cbb1.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "prerelease", - "comment": "Adds missing headers for HttpRequestHeaderCollection", - "packageName": "react-native-windows", - "email": "erozell@outlook.com", - "dependentChangeType": "patch" -} From e6961f6ad8bc9a4dc551f5ff3230c515d7e9d58f Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Mon, 15 Aug 2022 11:52:16 -0400 Subject: [PATCH 13/23] Skip user agent HTTP header validation (#10279) * Skip user agent HTTP header validation In #8392, we added logic to skip HTTP header validation for `User-Agent` in the NetworkingModule. Now that NetworkingModule is being refactored, we need this change in the new implementation. This change skips user agent validation in the new networking module. * Change files --- ...ative-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json | 7 +++++++ vnext/Shared/Networking/WinRTHttpResource.cpp | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 change/react-native-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json diff --git a/change/react-native-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json b/change/react-native-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json new file mode 100644 index 00000000000..cea86ab621c --- /dev/null +++ b/change/react-native-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Skip user agent HTTP header validation", + "packageName": "react-native-windows", + "email": "erozell@outlook.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index 0460e41d421..f6c84eb1426 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -219,6 +219,12 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque if (!success && m_onError) { co_return m_onError(coReqArgs->RequestId, "Failed to append Authorization"); } + } else if (boost::iequals(header.first.c_str(), "User-Agent")) { + bool success = + coRequest.Headers().TryAppendWithoutValidation(to_hstring(header.first), to_hstring(header.second)); + if (!success && m_onError) { + co_return m_onError(coReqArgs->RequestId, "Failed to append User-Agent"); + } } else { try { coRequest.Headers().Append(to_hstring(header.first), to_hstring(header.second)); From c17814030c9475fc454bcf55345a27e98fa76afd Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 15:59:30 -0700 Subject: [PATCH 14/23] Remove change files --- ...ative-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 change/react-native-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json diff --git a/change/react-native-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json b/change/react-native-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json deleted file mode 100644 index cea86ab621c..00000000000 --- a/change/react-native-windows-b0a84e25-6abf-44f7-ab49-48c8982eb792.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "prerelease", - "comment": "Skip user agent HTTP header validation", - "packageName": "react-native-windows", - "email": "erozell@outlook.com", - "dependentChangeType": "patch" -} From b0b101591d0b5164d8e2786d775d89385a626d87 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 16:00:47 -0700 Subject: [PATCH 15/23] Implement HTTP redirection (#10534) * Define WinRTHttpResult::CreateRequest * Use request produced by CreateRequest() TODO: Have PerformSendRequest receive raw method and uri instead of HttpRequestMessage. * Exit if request is not created successfully * Enabled FullCorsCrossOriginToAnotherCrossOriginRedirectWithPreflightSucceeds * Single retry * Add test RedirectPatchSucceeds * Rename tests to SimpleRedirectSucceeds * Use method and URI intead of full HTTPReqMsg in PerformSendReq * Move HttpResourceIntegrationTest into workspace * Add WinInet-based test * Get request complete variables * Define RequestContext struct * Get response content * Add synchronization logic * Refer CoreFX reference PR and version * Disable SimpleRedirectWinInetSucceeds * Define RedirectHttpFilter - Meant to be the default internal filter for WinRTHttpResource. * Use redirect filter for OP and default clients in factory * Implement RedirectHttpFilter::SendRequestAsync TODO: Deal with IDS_REQUEST_ALREADY_SENT by making CreateRequest coroutine available to both the resource and filter classes. * Expose resource as IWinRTHttpRequestFactory - Allows redir filter to access resource's request factory method. * Re-arrange resource instantiation in Make factory * Re-enable disabled Origin Policy tests * Make redir filter constructors noexcept * Attempt to implement IHttpBaseProtocolFilter * Make redir filter implement IHttpBaseProtocolFilter * Enable inheritance of IHttpBPFilter via unsetting WINRT_LEAN_AND_MEAN * Implement IHttpBPfilter based on inner filter * Add RedirHttpFilterUTs class * Fix comment * Consume mocks in MockBaseFilter * Implement mocks in ManualRedirectSucceeds * Implement manual redir test with coroutines * Complete [Manual|Automatic]RedirectSucceeds * Allow setting max redirect # in constructor - Add test TooManyRedirectsFails * Add test MaxAllowedRedirectsSucceeds * Minor requestArgs factoring * Define and consume IRedirectEventSource * Add IRedirectEventSource.idl to unit test project * Update Shared vcx filters * Partially implement OPFilter::OnRedirecting * Update Shared filters * Make OPFilter drop redirection for preflights * Allow empty string, non-null req content * Allow non-movable responses in test server (OPIntTests) * Always clear Http.OmitCredentials rt option * Update outdated comment * Removed commented code * Clean up stale/commented code * Throw E_INVALIDARG if redirect gets null request handle * Throw ERROR_HTTP_REDIRECT_FAILED on too many redirects * Remove/ignore incorrect tests * clang format * Change files * Update packages lock * Remove Redir filter constructor from DLL boundary * Drop unused libs/include dirs * Restore ut project IncludePath * Remove /*const*/ comments from HTTP mocks * Explicitly capture `constexpr` Implicit capture only available starting MSVC 14.3 * Declare redirect counts as size_t * Update packages.lock.json * Update packages lock * Replace IInspectable with WinRT IMap (request props) in CreateRequest * Make TaintedOrigin a direct request property. The `RequestArgs` struct should not hold Origin POlicy specific data. * clang format * Fix compilation of filter and resource in MSRN * Rename local variables * Fix relative include of WinRTTypes * Simplify redirect count tests * Propagate isTimeout to JS layer * Comment alternative HRESULTs for timeout * Address feedback for internal MIDL type * Update packages lock * Use std::stol to parse Content-Length * Use constexpr for default max redirects * Drop WinRT/Http/Filters header from PCH - This prevents including the header with WINRT_LEAN_AND_MEAN macro conflict. - Only DevSupportManager required it. Performance loss is negligible. * Add interface IRedirectEventSource2 * Remove IDL IRedirectEventSource * Rename IRedirectEventSource2 to IRedirectEventSource * Revert packages lock * Remove stale IDL reference * Throw on RedirectHttpFilter::AllowUI --- .../HttpOriginPolicyIntegrationTest.cpp | 41 +- .../HttpResourceIntegrationTests.cpp | 747 ++++++++++-------- .../OriginPolicyHttpFilterTest.cpp | 6 + .../React.Windows.Desktop.UnitTests.vcxproj | 3 +- ....Windows.Desktop.UnitTests.vcxproj.filters | 3 + .../RedirectHttpFilterUnitTest.cpp | 219 +++++ .../WinRTNetworkingMocks.cpp | 159 ++++ .../Desktop.UnitTests/WinRTNetworkingMocks.h | 191 ++++- vnext/Microsoft.ReactNative/Pch/pch.h | 1 - vnext/Shared/Modules/HttpModule.cpp | 6 +- vnext/Shared/Networking/IHttpResource.h | 2 +- .../Shared/Networking/IRedirectEventSource.h | 18 + .../Networking/IWinRTHttpRequestFactory.h | 22 + .../Networking/OriginPolicyHttpFilter.cpp | 63 +- .../Networking/OriginPolicyHttpFilter.h | 19 +- .../Shared/Networking/RedirectHttpFilter.cpp | 283 +++++++ vnext/Shared/Networking/RedirectHttpFilter.h | 97 +++ vnext/Shared/Networking/WinRTHttpResource.cpp | 361 +++++---- vnext/Shared/Networking/WinRTHttpResource.h | 21 +- vnext/Shared/Shared.vcxitems | 4 + vnext/Shared/Shared.vcxitems.filters | 12 + vnext/Test/HttpServer.cpp | 26 +- vnext/Test/HttpServer.h | 6 + 23 files changed, 1727 insertions(+), 583 deletions(-) create mode 100644 vnext/Desktop.UnitTests/RedirectHttpFilterUnitTest.cpp create mode 100644 vnext/Shared/Networking/IRedirectEventSource.h create mode 100644 vnext/Shared/Networking/IWinRTHttpRequestFactory.h create mode 100644 vnext/Shared/Networking/RedirectHttpFilter.cpp create mode 100644 vnext/Shared/Networking/RedirectHttpFilter.h diff --git a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp index 40c4fde98da..c9026cc9831 100644 --- a/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp +++ b/vnext/Desktop.IntegrationTests/HttpOriginPolicyIntegrationTest.cpp @@ -15,6 +15,7 @@ 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; @@ -82,7 +83,8 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) auto reqHandler = [&serverArgs](const DynamicRequest& request) -> ResponseWrapper { - return { std::move(serverArgs.Response) }; + // Don't use move constructor in case of multiple requests + return { serverArgs.Response }; }; switch (clientArgs.Method) @@ -133,7 +135,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) clientArgs.ResponseContent = std::move(content); clientArgs.ContentPromise.set_value(); }); - resource->SetOnError([&clientArgs](int64_t, string&& message) + resource->SetOnError([&clientArgs](int64_t, string&& message, bool) { clientArgs.ErrorMessage = std::move(message); clientArgs.ContentPromise.set_value(); @@ -144,7 +146,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) string{server1Args.Url}, 0, /*requestId*/ std::move(clientArgs.RequestHeaders), - {}, /*data*/ + dynamic::object("string", ""), /*data*/ "text", false, /*useIncrementalUpdates*/ 0, /*timeout*/ @@ -187,7 +189,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) clientArgs.ResponseContent = std::move(content); clientArgs.ContentPromise.set_value(); }); - resource->SetOnError([&clientArgs](int64_t, string&& message) + resource->SetOnError([&clientArgs](int64_t, string&& message, bool) { clientArgs.ErrorMessage = std::move(message); clientArgs.ContentPromise.set_value(); @@ -198,7 +200,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) string{serverArgs.Url}, 0, /*requestId*/ std::move(clientArgs.RequestHeaders), - {}, /*data*/ + dynamic::object("string", ""), /*data*/ "text", false, /*useIncrementalUpdates*/ 0, /*timeout*/ @@ -223,16 +225,16 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) TEST_METHOD_CLEANUP(MethodCleanup) { + // Clear any runtime options that may be used by tests in this class. SetRuntimeOptionInt("Http.OriginPolicy", static_cast(OriginPolicy::None)); SetRuntimeOptionString("Http.GlobalOrigin", {}); + SetRuntimeOptionBool("Http.OmitCredentials", false); // Bug in HttpServer does not correctly release TCP port between test methods. // Using a different por per test for now. s_port++; } - //TODO: NoCors_InvalidMethod_Failed? - BEGIN_TEST_METHOD_ATTRIBUTE(NoCorsForbiddenMethodSucceeds) // CONNECT, TRACE, and TRACK methods not supported by Windows.Web.Http // https://docs.microsoft.com/en-us/uwp/api/windows.web.http.httpmethod?view=winrt-19041#properties @@ -283,7 +285,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) getContent = std::move(content); getDataPromise.set_value(); }); - resource->SetOnError([&server, &error, &getDataPromise](int64_t, string&& message) + resource->SetOnError([&server, &error, &getDataPromise](int64_t, string&& message, bool) { error = std::move(message); getDataPromise.set_value(); @@ -296,8 +298,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) { {"ValidHeader", "AnyValue"} }, - {}, /*data*/ - //{} /*bodyData*/, + dynamic::object("string", ""), /*data*/ "text", false /*useIncrementalUpdates*/, 0 /*timeout*/, @@ -324,6 +325,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) SetRuntimeOptionString("Http.GlobalOrigin", s_crossOriginUrl); SetRuntimeOptionInt("Http.OriginPolicy", static_cast(OriginPolicy::SimpleCrossOriginResourceSharing)); + TestOriginPolicy(serverArgs, clientArgs, s_shouldFail); }// SimpleCorsForbiddenMethodFails @@ -344,8 +346,6 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) TestOriginPolicy(serverArgs, clientArgs, true /*shouldSucceed*/); }// NoCorsCrossOriginFetchRequestSucceeds - //NoCors_CrossOriginFetchRequestWithTimeout_Succeeded //TODO: Implement timeout - BEGIN_TEST_METHOD_ATTRIBUTE(NoCorsCrossOriginPatchSucceededs) END_TEST_METHOD_ATTRIBUTE() TEST_METHOD(NoCorsCrossOriginPatchSucceededs) @@ -377,6 +377,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) SetRuntimeOptionString("Http.GlobalOrigin", serverArgs.Url.c_str()); SetRuntimeOptionInt("Http.OriginPolicy", static_cast(OriginPolicy::SimpleCrossOriginResourceSharing)); + TestOriginPolicy(serverArgs, clientArgs, true /*shouldSucceed*/); }// SimpleCorsSameOriginSucceededs @@ -390,6 +391,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) SetRuntimeOptionString("Http.GlobalOrigin", s_crossOriginUrl); SetRuntimeOptionInt("Http.OriginPolicy", static_cast(OriginPolicy::SimpleCrossOriginResourceSharing)); + TestOriginPolicy(serverArgs, clientArgs, s_shouldFail); }// SimpleCorsCrossOriginFetchFails @@ -404,6 +406,7 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) SetRuntimeOptionString("Http.GlobalOrigin", serverArgs.Url.c_str()); SetRuntimeOptionInt("Http.OriginPolicy", static_cast(OriginPolicy::CrossOriginResourceSharing)); + TestOriginPolicy(serverArgs, clientArgs, true /*shouldSucceed*/); }// FullCorsSameOriginRequestSucceeds @@ -669,9 +672,6 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) } // FullCorsCrossOriginToAnotherCrossOriginRedirectSucceeds BEGIN_TEST_METHOD_ATTRIBUTE(FullCorsCrossOriginToAnotherCrossOriginRedirectWithPreflightSucceeds) - // [0x80072f88] The HTTP redirect request must be confirmed by the user - //TODO: Figure out manual redirection. - TEST_IGNORE() END_TEST_METHOD_ATTRIBUTE() TEST_METHOD(FullCorsCrossOriginToAnotherCrossOriginRedirectWithPreflightSucceeds) { @@ -763,8 +763,6 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) // The current implementation omits withCredentials flag from request and always sets it to false // Configure the responses for CORS request BEGIN_TEST_METHOD_ATTRIBUTE(FullCorsCrossOriginWithCredentialsSucceeds) - //TODO: Fails if run after FullCorsCrossOriginWithCredentialsFails - TEST_IGNORE() END_TEST_METHOD_ATTRIBUTE() TEST_METHOD(FullCorsCrossOriginWithCredentialsSucceeds) { @@ -823,15 +821,6 @@ TEST_CLASS(HttpOriginPolicyIntegrationTest) TestOriginPolicy(serverArgs, clientArgs, s_shouldFail); }// RequestWithProxyAuthorizationHeaderFails - - BEGIN_TEST_METHOD_ATTRIBUTE(ExceedingRedirectLimitFails) - TEST_IGNORE() - END_TEST_METHOD_ATTRIBUTE() - TEST_METHOD(ExceedingRedirectLimitFails) - { - Assert::Fail(L"NOT IMPLEMENTED"); - }// ExceedingRedirectLimitFails - }; uint16_t HttpOriginPolicyIntegrationTest::s_port = 7777; diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 7e071b84447..38d2d0d7592 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -34,43 +34,45 @@ using Test::EmptyResponse; using Test::HttpServer; using Test::ResponseWrapper; -TEST_CLASS (HttpResourceIntegrationTest) { - static uint16_t s_port; - - TEST_METHOD_CLEANUP(MethodCleanup) { - // Bug in WebSocketServer does not correctly release TCP port between test methods. - // Using a different por per test for now. - s_port++; - } - TEST_METHOD(RequestGetSucceeds) { - string url = "http://localhost:" + std::to_string(s_port); - - promise resPromise; - string error; - int statusCode = 0; - - auto server = make_shared(s_port); - server->Callbacks().OnGet = [&resPromise](const DynamicRequest &request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - response.body() = Test::CreateStringResponseBody("some response content"); - - return {std::move(response)}; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&resPromise, &statusCode](int64_t, IHttpResource::Response response) { - statusCode = static_cast(response.StatusCode); - }); - resource->SetOnData([&resPromise](int64_t, string &&content) { resPromise.set_value(); }); - resource->SetOnError([&resPromise, &error, &server](int64_t, string &&message) { - error = std::move(message); - resPromise.set_value(); +namespace Microsoft::React::Test { - server->Stop(); - }); - resource->SendRequest( + TEST_CLASS(HttpResourceIntegrationTest) { + static uint16_t s_port; + + TEST_METHOD_CLEANUP(MethodCleanup) { + // Bug in WebSocketServer does not correctly release TCP port between test methods. + // Using a different por per test for now. + s_port++; + } + TEST_METHOD(RequestGetSucceeds) { + string url = "http://localhost:" + std::to_string(s_port); + + promise resPromise; + string error; + int statusCode = 0; + + auto server = make_shared(s_port); + server->Callbacks().OnGet = [&resPromise](const DynamicRequest& request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + response.body() = Test::CreateStringResponseBody("some response content"); + + return { std::move(response) }; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&resPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + }); + resource->SetOnData([&resPromise](int64_t, string&& content) { resPromise.set_value(); }); + resource->SetOnError([&resPromise, &error, &server](int64_t, string&& message, bool) { + error = std::move(message); + resPromise.set_value(); + + server->Stop(); + }); + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -82,51 +84,51 @@ TEST_CLASS (HttpResourceIntegrationTest) { false /*withCredentials*/, [](int64_t) {}); - // Synchronize response. - resPromise.get_future().wait(); - server->Stop(); - - Assert::AreEqual({}, error); - Assert::AreEqual(200, statusCode); - } - - TEST_METHOD(RequestGetHeadersSucceeds) { - string url = "http://localhost:" + std::to_string(s_port); - - promise rcPromise; - string error; - IHttpResource::Response response; - - auto server = make_shared(s_port); - server->Callbacks().OnGet = [](const DynamicRequest &request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - - // Response header - response.set(http::field::server, "Microsoft::React::Test::HttpServer"); - // Response content header - response.set(http::field::content_length, "0"); - // Response arbitrary header - response.set("ResponseHeaderName1", "ResponseHeaderValue1"); - - return {std::move(response)}; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&rcPromise, &response](int64_t, IHttpResource::Response callbackResponse) { - response = callbackResponse; - rcPromise.set_value(); - }); - resource->SetOnError([&rcPromise, &error, &server](int64_t, string &&message) { - error = std::move(message); - rcPromise.set_value(); - - server->Abort(); - }); - - //clang-format off - resource->SendRequest( + // Synchronize response. + resPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({}, error); + Assert::AreEqual(200, statusCode); + } + + TEST_METHOD(RequestGetHeadersSucceeds) { + string url = "http://localhost:" + std::to_string(s_port); + + promise rcPromise; + string error; + IHttpResource::Response response; + + auto server = make_shared(s_port); + server->Callbacks().OnGet = [](const DynamicRequest& request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + + // Response header + response.set(http::field::server, "Microsoft::React::Test::HttpServer"); + // Response content header + response.set(http::field::content_length, "0"); + // Response arbitrary header + response.set("ResponseHeaderName1", "ResponseHeaderValue1"); + + return { std::move(response) }; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&rcPromise, &response](int64_t, IHttpResource::Response callbackResponse) { + response = callbackResponse; + rcPromise.set_value(); + }); + resource->SetOnError([&rcPromise, &error, &server](int64_t, string&& message, bool) { + error = std::move(message); + rcPromise.set_value(); + + server->Abort(); + }); + + //clang-format off + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -142,91 +144,95 @@ TEST_CLASS (HttpResourceIntegrationTest) { 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); - //clang-format on - - rcPromise.get_future().wait(); - server->Stop(); - - Assert::AreEqual({}, error, L"Error encountered"); - for (auto header : response.Headers) { - if (header.first == "Server") { - Assert::AreEqual({"Microsoft::React::Test::HttpServer"}, header.second, L"Wrong header"); - } else if (header.first == "Content-Length") { - Assert::AreEqual({"0"}, header.second, L"Wrong header"); - } else if (header.first == "ResponseHeaderName1") { - Assert::AreEqual({"ResponseHeaderValue1"}, header.second, L"Wrong header"); - } else { - string message = "Unexpected header: [" + header.first + "]=[" + header.second + "]"; - Assert::Fail(Microsoft::Common::Unicode::Utf8ToUtf16(message).c_str()); + //clang-format on + + rcPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({}, error, L"Error encountered"); + for (auto header : response.Headers) { + if (header.first == "Server") { + Assert::AreEqual({ "Microsoft::React::Test::HttpServer" }, header.second, L"Wrong header"); + } + else if (header.first == "Content-Length") { + Assert::AreEqual({ "0" }, header.second, L"Wrong header"); + } + else if (header.first == "ResponseHeaderName1") { + Assert::AreEqual({ "ResponseHeaderValue1" }, header.second, L"Wrong header"); + } + else { + string message = "Unexpected header: [" + header.first + "]=[" + header.second + "]"; + Assert::Fail(Microsoft::Common::Unicode::Utf8ToUtf16(message).c_str()); + } } } - } - - TEST_METHOD(RequestGetFails) { - string error; - promise promise; - - auto resource = IHttpResource::Make(); - resource->SetOnError([&error, &promise](int64_t, string &&message) { - error = message; - promise.set_value(); - }); - - resource->SendRequest("GET", "http://nonexistinghost", 0, {}, {}, "text", false, 0, false, [](int64_t) {}); - - promise.get_future().wait(); - - Logger::WriteMessage(error.c_str()); - Assert::AreNotEqual(string{}, error); - } - - TEST_METHOD(RequestOptionsSucceeds) { - string url = "http://localhost:" + std::to_string(s_port); - - promise getResponsePromise; - promise getDataPromise; - promise optionsPromise; - string error; - IHttpResource::Response getResponse; - IHttpResource::Response optionsResponse; - string content; - - auto server = make_shared(s_port); - server->Callbacks().OnOptions = [](const DynamicRequest &request) -> ResponseWrapper { - EmptyResponse response; - response.result(http::status::partial_content); - response.set("PreflightName", "PreflightValue"); - - return {std::move(response)}; - }; - server->Callbacks().OnGet = [](const DynamicRequest &request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - response.body() = Test::CreateStringResponseBody("Response Body"); - - return {std::move(response)}; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&getResponse, &getResponsePromise, &optionsResponse, &optionsPromise]( - int64_t, IHttpResource::Response callbackResponse) { - if (callbackResponse.StatusCode == static_cast(http::status::ok)) { - getResponse = callbackResponse; - getResponsePromise.set_value(); - } else if (callbackResponse.StatusCode == static_cast(http::status::partial_content)) { - optionsResponse = callbackResponse; - optionsPromise.set_value(); - } - }); - resource->SetOnData([&getDataPromise, &content](int64_t, string &&responseData) { - content = std::move(responseData); - - if (!content.empty()) - getDataPromise.set_value(); - }); - resource->SetOnError( - [&optionsPromise, &getResponsePromise, &getDataPromise, &error, &server](int64_t, string &&message) { + + TEST_METHOD(RequestGetFails) { + string error; + promise promise; + + auto resource = IHttpResource::Make(); + resource->SetOnError([&error, &promise](int64_t, string&& message, bool) { + error = message; + promise.set_value(); + }); + + resource->SendRequest("GET", "http://nonexistinghost", 0, {}, {}, "text", false, 0, false, [](int64_t) {}); + + promise.get_future().wait(); + + Logger::WriteMessage(error.c_str()); + Assert::AreNotEqual(string{}, error); + } + + TEST_METHOD(RequestOptionsSucceeds) { + string url = "http://localhost:" + std::to_string(s_port); + + promise getResponsePromise; + promise getDataPromise; + promise optionsPromise; + string error; + IHttpResource::Response getResponse; + IHttpResource::Response optionsResponse; + string content; + + auto server = make_shared(s_port); + server->Callbacks().OnOptions = [](const DynamicRequest& request) -> ResponseWrapper { + EmptyResponse response; + response.result(http::status::partial_content); + response.set("PreflightName", "PreflightValue"); + + return { std::move(response) }; + }; + server->Callbacks().OnGet = [](const DynamicRequest& request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + response.body() = Test::CreateStringResponseBody("Response Body"); + + return { std::move(response) }; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getResponse, &getResponsePromise, &optionsResponse, &optionsPromise]( + int64_t, IHttpResource::Response callbackResponse) { + if (callbackResponse.StatusCode == static_cast(http::status::ok)) { + getResponse = callbackResponse; + getResponsePromise.set_value(); + } + else if (callbackResponse.StatusCode == static_cast(http::status::partial_content)) { + optionsResponse = callbackResponse; + optionsPromise.set_value(); + } + }); + resource->SetOnData([&getDataPromise, &content](int64_t, string&& responseData) { + content = std::move(responseData); + + if (!content.empty()) + getDataPromise.set_value(); + }); + resource->SetOnError( + [&optionsPromise, &getResponsePromise, &getDataPromise, &error, &server](int64_t, string&& message, bool) { error = std::move(message); optionsPromise.set_value(); @@ -236,10 +242,10 @@ TEST_CLASS (HttpResourceIntegrationTest) { server->Stop(); }); - //clang-format off - resource->SendRequest( + //clang-format off + resource->SendRequest( "OPTIONS", - string{url}, + string{ url }, 0, /*requestId*/ {}, /*headers*/ {}, /*data*/ @@ -248,7 +254,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { 1000 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); - resource->SendRequest( + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -259,79 +265,80 @@ TEST_CLASS (HttpResourceIntegrationTest) { 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); - //clang-format on - - optionsPromise.get_future().wait(); - getResponsePromise.get_future().wait(); - getDataPromise.get_future().wait(); - server->Stop(); - - Assert::AreEqual({}, error, L"Error encountered"); - Assert::AreEqual(static_cast(1), optionsResponse.Headers.size()); - for (auto header : optionsResponse.Headers) { - if (header.first == "PreflightName") { - Assert::AreEqual({"PreflightValue"}, header.second, L"Wrong header"); - } else { - string message = "Unexpected header: [" + header.first + "]=[" + header.second + "]"; - Assert::Fail(Microsoft::Common::Unicode::Utf8ToUtf16(message).c_str()); + //clang-format on + + optionsPromise.get_future().wait(); + getResponsePromise.get_future().wait(); + getDataPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({}, error, L"Error encountered"); + Assert::AreEqual(static_cast(1), optionsResponse.Headers.size()); + for (auto header : optionsResponse.Headers) { + if (header.first == "PreflightName") { + Assert::AreEqual({ "PreflightValue" }, header.second, L"Wrong header"); + } + else { + string message = "Unexpected header: [" + header.first + "]=[" + header.second + "]"; + Assert::Fail(Microsoft::Common::Unicode::Utf8ToUtf16(message).c_str()); + } } + Assert::AreEqual({ "Response Body" }, content); } - Assert::AreEqual({"Response Body"}, content); - } - - TEST_METHOD(SimpleRedirectSucceeds) { - auto port1 = s_port; - auto port2 = ++s_port; - string url = "http://localhost:" + std::to_string(port1); - - promise getResponsePromise; - promise getContentPromise; - IHttpResource::Response getResponse; - string content; - string error; - - auto server1 = make_shared(port1); - server1->Callbacks().OnGet = [port2](const DynamicRequest &request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::moved_permanently); - response.set(http::field::location, {"http://localhost:" + std::to_string(port2)}); - - return {std::move(response)}; - }; - auto server2 = make_shared(port2); - server2->Callbacks().OnGet = [](const DynamicRequest &request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - response.body() = Test::CreateStringResponseBody("Redirect Content"); - - return {std::move(response)}; - }; - - server1->Start(); - server2->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&getResponse, &getResponsePromise](int64_t, IHttpResource::Response response) { - if (response.StatusCode == static_cast(http::status::ok)) { - getResponse = response; - getResponsePromise.set_value(); - } - }); - resource->SetOnData([&getContentPromise, &content](int64_t, string &&responseData) { - content = std::move(responseData); - - if (!content.empty()) - getContentPromise.set_value(); - }); - resource->SetOnError([&getResponsePromise, &getContentPromise, &error, &server1](int64_t, string &&message) { - error = std::move(message); - - getResponsePromise.set_value(); - getContentPromise.set_value(); - }); - - //clang-format off - resource->SendRequest( + + TEST_METHOD(SimpleRedirectGetSucceeds) { + auto port1 = s_port; + auto port2 = ++s_port; + string url = "http://localhost:" + std::to_string(port1); + + promise responsePromise; + promise contentPromise; + IHttpResource::Response responseResult; + string content; + string error; + + auto server1 = make_shared(port1); + server1->Callbacks().OnGet = [port2](const DynamicRequest& request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::moved_permanently); + response.set(http::field::location, { "http://localhost:" + std::to_string(port2) }); + + return { std::move(response) }; + }; + auto server2 = make_shared(port2); + server2->Callbacks().OnGet = [](const DynamicRequest& request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + response.body() = Test::CreateStringResponseBody("Redirect Content"); + + return { std::move(response) }; + }; + + server1->Start(); + server2->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&responseResult, &responsePromise](int64_t, IHttpResource::Response response) { + if (response.StatusCode == static_cast(http::status::ok)) { + responseResult = response; + responsePromise.set_value(); + } + }); + resource->SetOnData([&contentPromise, &content](int64_t, string&& responseData) { + content = std::move(responseData); + + if (!content.empty()) + contentPromise.set_value(); + }); + resource->SetOnError([&responsePromise, &contentPromise, &error, &server1](int64_t, string&& message, bool) { + error = std::move(message); + + responsePromise.set_value(); + contentPromise.set_value(); + }); + + //clang-format off + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -342,50 +349,128 @@ TEST_CLASS (HttpResourceIntegrationTest) { 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); - //clang-format on - - getResponsePromise.get_future().wait(); - getContentPromise.get_future().wait(); - - server2->Stop(); - server1->Stop(); - - Assert::AreEqual({}, error, L"Error encountered"); - Assert::AreEqual(static_cast(200), getResponse.StatusCode); - Assert::AreEqual({"Redirect Content"}, content); - } - - TEST_METHOD(TimeoutSucceeds) { - auto port = s_port; - string url = "http://localhost:" + std::to_string(port); - - promise getPromise; - string error; - int statusCode = 0; - - auto server = std::make_shared(s_port); - server->Callbacks().OnGet = [](const DynamicRequest &) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - - // Hold response to test client timeout - promise timer; - timer.get_future().wait_for(std::chrono::milliseconds(2000)); - - return {std::move(response)}; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { - statusCode = static_cast(response.StatusCode); - getPromise.set_value(); - }); - resource->SetOnError([&getPromise, &error](int64_t, string &&errorMessage) { - error = std::move(errorMessage); - getPromise.set_value(); - }); - resource->SendRequest( + //clang-format on + + responsePromise.get_future().wait(); + contentPromise.get_future().wait(); + + server2->Stop(); + server1->Stop(); + + Assert::AreEqual({}, error, L"Error encountered"); + Assert::AreEqual(static_cast(200), responseResult.StatusCode); + Assert::AreEqual({ "Redirect Content" }, content); + } + + TEST_METHOD(SimpleRedirectPatchSucceeds) { + auto port1 = s_port; + auto port2 = ++s_port; + string url = "http://localhost:" + std::to_string(port1); + + promise responsePromise; + promise contentPromise; + IHttpResource::Response responseResult; + string content; + string error; + + auto server1 = make_shared(port1); + server1->Callbacks().OnPatch = [port2](const DynamicRequest& request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::moved_permanently); + response.set(http::field::location, { "http://localhost:" + std::to_string(port2) }); + + return { std::move(response) }; + }; + auto server2 = make_shared(port2); + server2->Callbacks().OnPatch = [](const DynamicRequest& request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + response.body() = Test::CreateStringResponseBody("Redirect Content"); + + return { std::move(response) }; + }; + + server1->Start(); + server2->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&responseResult, &responsePromise](int64_t, IHttpResource::Response response) { + if (response.StatusCode == static_cast(http::status::ok)) { + responseResult = response; + responsePromise.set_value(); + } + }); + resource->SetOnData([&contentPromise, &content](int64_t, string&& responseData) { + content = std::move(responseData); + + if (!content.empty()) + contentPromise.set_value(); + }); + resource->SetOnError([&responsePromise, &contentPromise, &error, &server1](int64_t, string&& message, bool) { + error = std::move(message); + + responsePromise.set_value(); + contentPromise.set_value(); + }); + + //clang-format off + resource->SendRequest( + "PATCH", + std::move(url), + 0, /*requestId*/ + {}, /*headers*/ + {}, /*data*/ + "text", + false, /*useIncrementalUpdates*/ + 0 /*timeout*/, + false /*withCredentials*/, + [](int64_t) {}); + //clang-format on + + responsePromise.get_future().wait(); + contentPromise.get_future().wait(); + + server2->Stop(); + server1->Stop(); + + Assert::AreEqual({}, error, L"Error encountered"); + Assert::AreEqual(static_cast(200), responseResult.StatusCode); + Assert::AreEqual({ "Redirect Content" }, content); + } + + TEST_METHOD(TimeoutSucceeds) { + auto port = s_port; + string url = "http://localhost:" + std::to_string(port); + + promise getPromise; + string error; + int statusCode = 0; + bool timeoutError = false; + + auto server = std::make_shared(s_port); + server->Callbacks().OnGet = [](const DynamicRequest&) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + + // Hold response to test client timeout + promise timer; + timer.get_future().wait_for(std::chrono::milliseconds(2000)); + + return { std::move(response) }; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + getPromise.set_value(); + }); + resource->SetOnError([&getPromise, &error, &timeoutError](int64_t, string&& errorMessage, bool isTimeout) { + error = std::move(errorMessage); + timeoutError = isTimeout; + getPromise.set_value(); + }); + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -397,44 +482,47 @@ TEST_CLASS (HttpResourceIntegrationTest) { false, /*withCredentials*/ [](int64_t) {} /*callback*/); - getPromise.get_future().wait(); - server->Stop(); - - Assert::AreEqual({}, error); - Assert::AreEqual(200, statusCode); - } - - TEST_METHOD(TimeoutFails) { - auto port = s_port; - string url = "http://localhost:" + std::to_string(port); - - promise getPromise; - string error; - int statusCode = 0; - - auto server = std::make_shared(s_port); - server->Callbacks().OnGet = [](const DynamicRequest &) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - - // Hold response to test client timeout - promise timer; - timer.get_future().wait_for(std::chrono::milliseconds(4000)); - - return {std::move(response)}; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { - statusCode = static_cast(response.StatusCode); - getPromise.set_value(); - }); - resource->SetOnError([&getPromise, &error](int64_t, string &&errorMessage) { - error = std::move(errorMessage); - getPromise.set_value(); - }); - resource->SendRequest( + getPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({}, error); + Assert::IsFalse(timeoutError); + Assert::AreEqual(200, statusCode); + } + + TEST_METHOD(TimeoutFails) { + auto port = s_port; + string url = "http://localhost:" + std::to_string(port); + + promise getPromise; + string error; + int statusCode = 0; + bool timeoutError = false; + + auto server = std::make_shared(s_port); + server->Callbacks().OnGet = [](const DynamicRequest&) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + + // Hold response to test client timeout + promise timer; + timer.get_future().wait_for(std::chrono::milliseconds(4000)); + + return { std::move(response) }; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + getPromise.set_value(); + }); + resource->SetOnError([&getPromise, &error, &timeoutError](int64_t, string&& errorMessage, bool isTimeout) { + error = std::move(errorMessage); + timeoutError = isTimeout; + getPromise.set_value(); + }); + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -446,14 +534,15 @@ TEST_CLASS (HttpResourceIntegrationTest) { false, /*withCredentials*/ [](int64_t) {} /*callback*/); - getPromise.get_future().wait(); - server->Stop(); + getPromise.get_future().wait(); + server->Stop(); - Assert::AreEqual({"[0x800705b4] This operation returned because the timeout period expired."}, error); - Assert::AreEqual(0, statusCode); - } -}; + Assert::IsTrue(timeoutError); + Assert::AreEqual({ "[0x800705b4] This operation returned because the timeout period expired." }, error); + Assert::AreEqual(0, statusCode); + } + }; -/*static*/ uint16_t HttpResourceIntegrationTest::s_port = 4444; + /*static*/ uint16_t HttpResourceIntegrationTest::s_port = 4444; -/*static*/ uint16_t HttpResourceIntegrationTest::s_port = 4444; +} // namespace Microsoft::React::Test diff --git a/vnext/Desktop.UnitTests/OriginPolicyHttpFilterTest.cpp b/vnext/Desktop.UnitTests/OriginPolicyHttpFilterTest.cpp index 65fec288b1b..a31afb0ff05 100644 --- a/vnext/Desktop.UnitTests/OriginPolicyHttpFilterTest.cpp +++ b/vnext/Desktop.UnitTests/OriginPolicyHttpFilterTest.cpp @@ -53,6 +53,12 @@ TEST_CLASS (OriginPolicyHttpFilterTest) { Assert::IsTrue (OriginPolicyHttpFilter::IsSameOrigin(Uri{L"http://[2001:db8:1f70::999:de8:7648:6e8]"}, Uri{L"http://[2001:db8:1f70::999:de8:7648:6e8]/test.html"})); Assert::IsTrue (OriginPolicyHttpFilter::IsSameOrigin(Uri{L"https://\u65E5\u672C\u8A9E.com" }, Uri{L"https://\u65E5\u672C\u8A9E.com/FakeResponse.ashx"})); Assert::IsTrue (OriginPolicyHttpFilter::IsSameOrigin(Uri{L"https://www.microsoft.com" }, Uri{L"https://www.microsoft.com:443"})); + Assert::IsFalse(OriginPolicyHttpFilter::IsSameOrigin(Uri{L"https://www.microsoft.com" }, Uri{nullptr})); + Assert::IsFalse(OriginPolicyHttpFilter::IsSameOrigin(Uri{nullptr }, Uri{L"https://www.microsoft.com"})); + Assert::IsFalse(OriginPolicyHttpFilter::IsSameOrigin(Uri{nullptr }, Uri{nullptr})); + Assert::IsFalse(OriginPolicyHttpFilter::IsSameOrigin(Uri{L"https://www.microsoft.com" }, nullptr)); + Assert::IsFalse(OriginPolicyHttpFilter::IsSameOrigin(nullptr , Uri{L"https://www.microsoft.com"})); + Assert::IsFalse(OriginPolicyHttpFilter::IsSameOrigin(nullptr , nullptr)); // clang-format on } diff --git a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj index 87b2757f01e..0c48bd6ceeb 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)Shared;$(ReactNativeWindowsDir)include\Shared;$(MSBuildThisFileDirectory);$(IncludePath) + $(ReactNativeWindowsDir)Mso;$(ReactNativeWindowsDir)Common;$(ReactNativeWindowsDir)Desktop;$(ReactNativeWindowsDir)stubs;$(ReactNativeWindowsDir)Shared;$(ReactNativeWindowsDir)include\Shared;$(MSBuildThisFileDirectory);$(IncludePath) @@ -94,6 +94,7 @@ + diff --git a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj.filters b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj.filters index 5824ed65aa1..05751912951 100644 --- a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj.filters +++ b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj.filters @@ -61,6 +61,9 @@ Unit Tests + + Unit Tests + diff --git a/vnext/Desktop.UnitTests/RedirectHttpFilterUnitTest.cpp b/vnext/Desktop.UnitTests/RedirectHttpFilterUnitTest.cpp new file mode 100644 index 00000000000..920a3ca918a --- /dev/null +++ b/vnext/Desktop.UnitTests/RedirectHttpFilterUnitTest.cpp @@ -0,0 +1,219 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include + +#include +#include +#include "WinRTNetworkingMocks.h" + +// Windows API +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace winrt::Windows::Web::Http; + +using Microsoft::React::Networking::RedirectHttpFilter; +using Microsoft::React::Networking::ResponseOperation; +using winrt::Windows::Foundation::Uri; +using winrt::Windows::Web::Http::Filters::IHttpBaseProtocolFilter; +using winrt::Windows::Web::Http::Filters::IHttpFilter; + +namespace Microsoft::VisualStudio::CppUnitTestFramework { + +template <> +std::wstring ToString(const HttpStatusCode &status) { + return ToString(static_cast(status)); +} + +} // namespace Microsoft::VisualStudio::CppUnitTestFramework + +namespace Microsoft::React::Test { + +TEST_CLASS (RedirectHttpFilterUnitTest) { + TEST_CLASS_INITIALIZE(Initialize) { + winrt::uninit_apartment(); // Why does this work? + } + + TEST_METHOD(QueryInterfacesSucceeds) { + auto filter = winrt::make(); + + auto iFilter = filter.try_as(); + Assert::IsFalse(iFilter == nullptr); + auto baseFilter = filter.try_as(); + Assert::IsFalse(baseFilter == nullptr); + } + + TEST_METHOD(AutomaticRedirectSucceeds) { + auto url1 = L"http://initialhost"; + auto url2 = L"http://redirecthost"; + auto mockFilter1 = winrt::make(); + auto mockFilter2 = winrt::make(); + mockFilter1.as()->Mocks.SendRequestAsync = + [&url2](HttpRequestMessage const &request) -> ResponseOperation { + HttpResponseMessage response{}; + + if (request.RequestUri().Host() == L"initialhost") { + response.StatusCode(HttpStatusCode::MovedPermanently); + response.Headers().Location(Uri{url2}); + response.Content(HttpStringContent{L""}); + } else { + response.StatusCode(HttpStatusCode::BadRequest); + response.Content(HttpStringContent{L""}); + } + + co_return response; + }; + mockFilter2.as()->Mocks.SendRequestAsync = + [](HttpRequestMessage const &request) -> ResponseOperation { + HttpResponseMessage response; + + if (request.RequestUri().Host() == L"redirecthost") { + response.StatusCode(HttpStatusCode::Ok); + response.Content(HttpStringContent{L"Response Content"}); + } else { + response.StatusCode(HttpStatusCode::BadRequest); + response.Content(HttpStringContent{L""}); + } + + co_return response; + }; + + auto filter = winrt::make(std::move(mockFilter1), std::move(mockFilter2)); + auto client = HttpClient{filter}; + auto request = HttpRequestMessage{HttpMethod::Get(), Uri{url1}}; + auto sendOp = client.SendRequestAsync(request); + sendOp.get(); + auto response = sendOp.GetResults(); + + Assert::AreEqual(HttpStatusCode::Ok, response.StatusCode()); + + auto contentOp = response.Content().ReadAsStringAsync(); + contentOp.get(); + auto content = contentOp.GetResults(); + Assert::AreEqual(L"Response Content", content.c_str()); + } + + TEST_METHOD(ManualRedirectSucceeds) { + auto url1 = L"http://initialhost"; + auto url2 = L"http://redirecthost"; + auto mockFilter1 = winrt::make(); + auto mockFilter2 = winrt::make(); + mockFilter1.as()->Mocks.SendRequestAsync = + [&url2](HttpRequestMessage const &request) -> ResponseOperation { + HttpResponseMessage response{}; + + if (request.RequestUri().Host() == L"initialhost") { + response.StatusCode(HttpStatusCode::MovedPermanently); + response.Headers().Location(Uri{url2}); + response.Content(HttpStringContent{L""}); + } else if (request.RequestUri().Host() == L"redirecthost") { + response.StatusCode(HttpStatusCode::Ok); + response.Content(HttpStringContent{L"Response Content"}); + } + + co_return response; + }; + + auto filter = winrt::make(std::move(mockFilter1), std::move(mockFilter2)); + // Disable automatic redirect + filter.try_as().AllowAutoRedirect(false); + + auto client = HttpClient{filter}; + auto request = HttpRequestMessage{HttpMethod::Get(), Uri{url1}}; + auto sendOp = client.SendRequestAsync(request); + sendOp.get(); + auto response = sendOp.GetResults(); + + Assert::AreEqual(HttpStatusCode::MovedPermanently, response.StatusCode()); + + request = HttpRequestMessage{response.RequestMessage().Method(), response.Headers().Location()}; + sendOp = client.SendRequestAsync(request); + sendOp.get(); + response = sendOp.GetResults(); + + Assert::AreEqual(HttpStatusCode::Ok, response.StatusCode()); + + auto contentOp = response.Content().ReadAsStringAsync(); + contentOp.get(); + auto content = contentOp.GetResults(); + Assert::AreEqual(L"Response Content", content.c_str()); + } + + void TestRedirectCount(size_t maxRedirects, size_t actualRedirects) { + auto url1 = L"http://initialhost"; + auto url2 = L"http://redirecthost"; + auto mockFilter1 = winrt::make(); + auto mockFilter2 = winrt::make(); + + size_t redirectCount = 0; + mockFilter1.as()->Mocks.SendRequestAsync = + [&url2, &redirectCount](auto const &request) -> ResponseOperation { + HttpResponseMessage response; + + response.Headers().Location(Uri{url2}); + response.StatusCode(HttpStatusCode::MovedPermanently); + redirectCount++; + + co_return response; + }; + mockFilter2.as()->Mocks.SendRequestAsync = + [&url2, &redirectCount, actualRedirects](auto const &request) -> ResponseOperation { + HttpResponseMessage response; + + if (redirectCount >= actualRedirects) { + response.StatusCode(HttpStatusCode::Ok); + response.Content(HttpStringContent{L"Response Content"}); + } else { + response.Headers().Location(Uri{url2}); + response.StatusCode(HttpStatusCode::MovedPermanently); + redirectCount++; + } + + co_return response; + }; + + auto filter = winrt::make(maxRedirects, std::move(mockFilter1), std::move(mockFilter2)); + auto client = HttpClient{filter}; + auto request = HttpRequestMessage{HttpMethod::Get(), Uri{url1}}; + ResponseOperation sendOp = nullptr; + long errorCode = 0; + winrt::hstring errorMessage{}; + try { + sendOp = client.SendRequestAsync(request); + sendOp.get(); + } catch (const winrt::hresult_error &e) { + errorCode = e.code(); + errorMessage = e.message(); + } + + if (maxRedirects >= actualRedirects) { + // Should succeed + auto response = sendOp.GetResults(); + + Assert::AreEqual(0, (int)errorCode); + Assert::AreEqual(L"", errorMessage.c_str()); + Assert::AreEqual(HttpStatusCode::Ok, response.StatusCode()); + + auto contentOp = response.Content().ReadAsStringAsync(); + contentOp.get(); + auto content = contentOp.GetResults(); + Assert::AreEqual(L"Response Content", content.c_str()); + } else { + // Should fail + Assert::AreEqual(HRESULT_FROM_WIN32(ERROR_HTTP_REDIRECT_FAILED), errorCode); + Assert::AreEqual(L"Too many redirects", errorMessage.c_str()); + } + } + + TEST_METHOD(MaxAllowedRedirectsSucceeds) { + TestRedirectCount(3, 3); + } + + TEST_METHOD(TooManyRedirectsFails) { + TestRedirectCount(2, 3); + } +}; + +} // namespace Microsoft::React::Test diff --git a/vnext/Desktop.UnitTests/WinRTNetworkingMocks.cpp b/vnext/Desktop.UnitTests/WinRTNetworkingMocks.cpp index 98e23a18ef5..0621d8cd6b7 100644 --- a/vnext/Desktop.UnitTests/WinRTNetworkingMocks.cpp +++ b/vnext/Desktop.UnitTests/WinRTNetworkingMocks.cpp @@ -7,12 +7,16 @@ using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Networking::Sockets; +using namespace winrt::Windows::Security::Cryptography::Certificates; using namespace winrt::Windows::Storage::Streams; +using namespace winrt::Windows::Web::Http; using std::exception; using winrt::auto_revoke_t; using winrt::event_token; using winrt::param::hstring; +using winrt::Windows::Foundation::Collections::IVector; +using winrt::Windows::Security::Credentials::PasswordCredential; namespace Microsoft::React::Test { @@ -371,4 +375,159 @@ void MockMessageWebSocketControl::MessageType(SocketMessageType const &value) co #endif // 0 +#pragma region MockHttpBaseFilter + +MockHttpBaseFilter::MockHttpBaseFilter() noexcept {} + +#pragma region IHttpFilter + +IAsyncOperationWithProgress MockHttpBaseFilter::SendRequestAsync( + HttpRequestMessage const &request) { + if (Mocks.SendRequestAsync) + return Mocks.SendRequestAsync(request); + + throw exception("Not implemented"); +} + +#pragma endregion IHttpFilter + +#pragma region IHttpBaseProtocolFilter + +bool MockHttpBaseFilter::AllowAutoRedirect() const { + if (Mocks.GetAllowAutoRedirect) + return Mocks.GetAllowAutoRedirect(); + + throw exception("Not implemented"); +} + +void MockHttpBaseFilter::AllowAutoRedirect(bool value) const { + if (Mocks.SetAllowAutoRedirect) + return Mocks.SetAllowAutoRedirect(value); + + throw exception("Not implemented"); +} + +bool MockHttpBaseFilter::AllowUI() const { + if (Mocks.GetAllowUI) + return Mocks.GetAllowUI(); + + throw exception("Not implemented"); +} + +void MockHttpBaseFilter::AllowUI(bool value) const { + if (Mocks.SetAllowUI) + return Mocks.SetAllowUI(value); + + throw exception("Not implemented"); +} + +bool MockHttpBaseFilter::AutomaticDecompression() const { + if (Mocks.GetAutomaticDecompression) + return Mocks.GetAutomaticDecompression(); + + throw exception("Not implemented"); +} + +void MockHttpBaseFilter::AutomaticDecompression(bool value) const { + if (Mocks.SetAutomaticDecompression) + return Mocks.SetAutomaticDecompression(value); + + throw exception("Not implemented"); +} + +Filters::HttpCacheControl MockHttpBaseFilter::CacheControl() const { + if (Mocks.GetCacheControl) + return Mocks.GetCacheControl(); + + throw exception("Not implemented"); +} + +HttpCookieManager MockHttpBaseFilter::CookieManager() const { + if (Mocks.GetCookieManager) + return Mocks.GetCookieManager(); + + throw exception("Not implemented"); +} + +Certificate MockHttpBaseFilter::ClientCertificate() const { + if (Mocks.GetClientCertificate) + return Mocks.GetClientCertificate(); + + throw exception("Not implemented"); +} + +void MockHttpBaseFilter::ClientCertificate(Certificate const &value) const { + if (Mocks.SetClientCertificate) + return Mocks.SetClientCertificate(value); + + throw exception("Not implemented"); +} + +IVector MockHttpBaseFilter::IgnorableServerCertificateErrors() const { + if (Mocks.GetIgnorableServerCertificateErrors) + return Mocks.GetIgnorableServerCertificateErrors(); + + throw exception("Not implemented"); +} + +uint32_t MockHttpBaseFilter::MaxConnectionsPerServer() const { + if (Mocks.GetMaxConnectionsPerServer) + return Mocks.GetMaxConnectionsPerServer(); + + throw exception("Not implemented"); +} + +void MockHttpBaseFilter::MaxConnectionsPerServer(uint32_t value) const { + if (Mocks.SetMaxConnectionsPerServer) + return Mocks.SetMaxConnectionsPerServer(value); + + throw exception("Not implemented"); +} + +PasswordCredential MockHttpBaseFilter::ProxyCredential() const { + if (Mocks.GetProxyCredential) + return Mocks.GetProxyCredential(); + + throw exception("Not implemented"); +} + +void MockHttpBaseFilter::ProxyCredential(PasswordCredential const &value) const { + if (Mocks.SetProxyCredential) + return Mocks.SetProxyCredential(value); + + throw exception("Not implemented"); +} + +PasswordCredential MockHttpBaseFilter::ServerCredential() const { + if (Mocks.GetServerCredential) + return Mocks.GetServerCredential(); + + throw exception("Not implemented"); +} + +void MockHttpBaseFilter::ServerCredential(PasswordCredential const &value) const { + if (Mocks.SetServerCredential) + return Mocks.SetServerCredential(value); + + throw exception("Not implemented"); +} + +bool MockHttpBaseFilter::UseProxy() const { + if (Mocks.GetUseProxy) + return Mocks.GetUseProxy(); + + throw exception("Not implemented"); +} + +void MockHttpBaseFilter::UseProxy(bool value) const { + if (Mocks.SetUseProxy) + return Mocks.SetUseProxy(value); + + throw exception("Not implemented"); +} + +#pragma endregion IHttpBaseProtocolFilter + +#pragma endregion MockHttpBaseFilter + } // namespace Microsoft::React::Test diff --git a/vnext/Desktop.UnitTests/WinRTNetworkingMocks.h b/vnext/Desktop.UnitTests/WinRTNetworkingMocks.h index 9fe166dac9c..b16f1219182 100644 --- a/vnext/Desktop.UnitTests/WinRTNetworkingMocks.h +++ b/vnext/Desktop.UnitTests/WinRTNetworkingMocks.h @@ -3,7 +3,15 @@ #pragma once +// Windows API +#include #include +#include +#include +#include +#include + +// Standard Library #include namespace Microsoft::React::Test { @@ -20,45 +28,44 @@ struct MockMessageWebSocket : public winrt::implements< struct Mocks { // IWebSocket - std::function - ConnectAsync; + std::function ConnectAsync; - std::function SetRequestHeader; + std::function SetRequestHeader; - std::function OutputStream; + std::function OutputStream; - std::function Close; + std::function Close; std::function const &) /*const*/> + winrt::Windows::Networking::Sockets::WebSocketClosedEventArgs> const &)> ClosedToken; std::function const &) /*const*/> + winrt::Windows::Networking::Sockets::WebSocketClosedEventArgs> const &)> ClosedRevoker; - std::function ClosedVoid; + std::function ClosedVoid; // IMessageWebSocket - std::function Control; + std::function Control; - std::function Information; + std::function Information; std::function const &) /*const*/> + winrt::Windows::Networking::Sockets::MessageWebSocketMessageReceivedEventArgs> const &)> MessageReceivedToken; std::function const &) /*const*/> + winrt::Windows::Networking::Sockets::MessageWebSocketMessageReceivedEventArgs> const &)> MessageReceivedRevoker; std::function MessageReceivedVoid; @@ -125,37 +132,35 @@ struct ThrowingMessageWebSocket : public MockMessageWebSocket { struct MockDataWriter : public winrt::Windows::Storage::Streams::IDataWriter { struct Mocks { - std::function UnstoredBufferLength; - std::function GetUnicodeEncoding; - std::function SetUnicodeEncoding; - std::function GetByteOrder; - std::function SetByteOrder; - std::function WriteByte; - std::function value) /*const*/> WriteBytes; - std::function WriteBuffer; - std::function + std::function UnstoredBufferLength; + std::function GetUnicodeEncoding; + std::function SetUnicodeEncoding; + std::function GetByteOrder; + std::function SetByteOrder; + std::function WriteByte; + std::function value)> WriteBytes; + std::function WriteBuffer; + std::function< + void(winrt::Windows::Storage::Streams::IBuffer const &buffer, std::uint32_t start, std::uint32_t count)> WriteBufferRange; - std::function WriteBoolean; - std::function WriteGuid; - std::function WriteInt16; - std::function WriteInt32; - std::function WriteInt64; - std::function WriteUInt16; - std::function WriteUInt32; - std::function WriteUInt64; - std::function WriteSingle; - std::function WriteDouble; - std::function WriteDateTime; - std::function WriteTimeSpan; - std::function WriteString; - std::function MeasureString; - std::function StoreAsync; - std::function() /*const*/> FlushAsync; - std::function DetachBuffer; - std::function DetachStream; + std::function WriteBoolean; + std::function WriteGuid; + std::function WriteInt16; + std::function WriteInt32; + std::function WriteInt64; + std::function WriteUInt16; + std::function WriteUInt32; + std::function WriteUInt64; + std::function WriteSingle; + std::function WriteDouble; + std::function WriteDateTime; + std::function WriteTimeSpan; + std::function WriteString; + std::function MeasureString; + std::function StoreAsync; + std::function()> FlushAsync; + std::function DetachBuffer; + std::function DetachStream; }; Mocks Mocks; @@ -221,4 +226,106 @@ struct MockMessageWebSocketControl : winrt::implements< #pragma endregion }; +struct MockHttpBaseFilter : public winrt::implements< + MockHttpBaseFilter, + winrt::Windows::Web::Http::Filters::IHttpFilter, + winrt::Windows::Web::Http::Filters::IHttpBaseProtocolFilter> { + struct Mocks { +#pragma region IHttpFilter + + std::function(winrt::Windows::Web::Http::HttpRequestMessage const &request)> + SendRequestAsync; + +#pragma endregion IHttpFilter + +#pragma region IHttpBaseProtocolFilter + + std::function GetAllowAutoRedirect; + std::function SetAllowAutoRedirect{[](bool) {}}; + + std::function GetAllowUI; + std::function SetAllowUI{[](bool) {}}; + + std::function GetAutomaticDecompression; + std::function SetAutomaticDecompression; + + std::function GetCacheControl; + + std::function GetCookieManager; + + std::function GetClientCertificate; + std::function + SetClientCertificate; + + std::function()> + GetIgnorableServerCertificateErrors; + + std::function GetMaxConnectionsPerServer; + std::function SetMaxConnectionsPerServer; + + std::function GetProxyCredential; + std::function SetProxyCredential; + + std::function GetServerCredential; + std::function SetServerCredential; + + std::function GetUseProxy; + std::function SetUseProxy; + +#pragma endregion IHttpBaseProtocolFilter + }; + + Mocks Mocks; + + MockHttpBaseFilter() noexcept; + +#pragma region IHttpFilter + + winrt::Windows::Foundation::IAsyncOperationWithProgress< + winrt::Windows::Web::Http::HttpResponseMessage, + winrt::Windows::Web::Http::HttpProgress> + SendRequestAsync(winrt::Windows::Web::Http::HttpRequestMessage const &request); + +#pragma endregion IHttpFilter + +#pragma region IHttpBaseProtocolFilter + + bool AllowAutoRedirect() const; + void AllowAutoRedirect(bool value) const; + + bool AllowUI() const; + void AllowUI(bool value) const; + + bool AutomaticDecompression() const; + void AutomaticDecompression(bool value) const; + + winrt::Windows::Web::Http::Filters::HttpCacheControl CacheControl() const; + + winrt::Windows::Web::Http::HttpCookieManager CookieManager() const; + + winrt::Windows::Security::Cryptography::Certificates::Certificate ClientCertificate() const; + void ClientCertificate(winrt::Windows::Security::Cryptography::Certificates::Certificate const &value) const; + + winrt::Windows::Foundation::Collections::IVector< + winrt::Windows::Security::Cryptography::Certificates::ChainValidationResult> + IgnorableServerCertificateErrors() const; + + uint32_t MaxConnectionsPerServer() const; + void MaxConnectionsPerServer(uint32_t value) const; + + winrt::Windows::Security::Credentials::PasswordCredential ProxyCredential() const; + void ProxyCredential(winrt::Windows::Security::Credentials::PasswordCredential const &value) const; + + winrt::Windows::Security::Credentials::PasswordCredential ServerCredential() const; + void ServerCredential(winrt::Windows::Security::Credentials::PasswordCredential const &value) const; + + bool UseProxy() const; + void UseProxy(bool value) const; + +#pragma endregion IHttpBaseProtocolFilter +}; + } // namespace Microsoft::React::Test diff --git a/vnext/Microsoft.ReactNative/Pch/pch.h b/vnext/Microsoft.ReactNative/Pch/pch.h index bc1937d2613..34b0b02b9be 100644 --- a/vnext/Microsoft.ReactNative/Pch/pch.h +++ b/vnext/Microsoft.ReactNative/Pch/pch.h @@ -43,7 +43,6 @@ #include #include #include -#include #include #include "Base/CxxReactIncludes.h" diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 7132f072b16..c3a5be069b8 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -72,9 +72,11 @@ namespace { }; resource->SetOnData(std::move(onDataDynamic)); - resource->SetOnError([weakReactInstance](int64_t requestId, string&& message) { + resource->SetOnError([weakReactInstance](int64_t requestId, string&& message, bool isTimeout) { dynamic args = dynamic::array(requestId, std::move(message)); - // TODO: isTimeout errorArgs.push_back(true); + if (isTimeout) { + args.push_back(true); + } SendEvent(weakReactInstance, completedResponse, std::move(args)); }); diff --git a/vnext/Shared/Networking/IHttpResource.h b/vnext/Shared/Networking/IHttpResource.h index 173c79cda6e..17d791d1868 100644 --- a/vnext/Shared/Networking/IHttpResource.h +++ b/vnext/Shared/Networking/IHttpResource.h @@ -96,7 +96,7 @@ struct IHttpResource { virtual void SetOnData(std::function &&handler) noexcept = 0; virtual void SetOnData(std::function &&handler) noexcept = 0; virtual void SetOnError( - std::function &&handler) noexcept = 0; + std::function &&handler) noexcept = 0; }; } // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/IRedirectEventSource.h b/vnext/Shared/Networking/IRedirectEventSource.h new file mode 100644 index 00000000000..90e18fcccbe --- /dev/null +++ b/vnext/Shared/Networking/IRedirectEventSource.h @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include + +namespace Microsoft::React::Networking { + +struct IRedirectEventSource : winrt::implements { + virtual bool OnRedirecting( + winrt::Windows::Web::Http::HttpRequestMessage const &request, + winrt::Windows::Web::Http::HttpResponseMessage const &response) noexcept = 0; +}; + +} // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/IWinRTHttpRequestFactory.h b/vnext/Shared/Networking/IWinRTHttpRequestFactory.h new file mode 100644 index 00000000000..f6c060e737d --- /dev/null +++ b/vnext/Shared/Networking/IWinRTHttpRequestFactory.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include + +namespace Microsoft::React::Networking { + +struct IWinRTHttpRequestFactory { + virtual ~IWinRTHttpRequestFactory() noexcept {} + + virtual winrt::Windows::Foundation::IAsyncOperation CreateRequest( + winrt::Windows::Web::Http::HttpMethod &&method, + winrt::Windows::Foundation::Uri &&uri, + winrt::Windows::Foundation::Collections::IMap + props) noexcept = 0; +}; + +} // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/OriginPolicyHttpFilter.cpp b/vnext/Shared/Networking/OriginPolicyHttpFilter.cpp index f98f07e67aa..bb902db79cb 100644 --- a/vnext/Shared/Networking/OriginPolicyHttpFilter.cpp +++ b/vnext/Shared/Networking/OriginPolicyHttpFilter.cpp @@ -11,10 +11,6 @@ #include #include -// Windows API -#include -#include - // Standard Library #include #include @@ -28,6 +24,7 @@ using winrt::to_hstring; using winrt::Windows::Foundation::IInspectable; using winrt::Windows::Foundation::IPropertyValue; using winrt::Windows::Foundation::Uri; +using winrt::Windows::Foundation::Collections::IMap; using winrt::Windows::Web::Http::HttpMethod; using winrt::Windows::Web::Http::HttpRequestMessage; using winrt::Windows::Web::Http::HttpResponseMessage; @@ -119,7 +116,7 @@ bool OriginPolicyHttpFilter::ConstWcharComparer::operator()(const wchar_t *a, co } /*static*/ bool OriginPolicyHttpFilter::IsSameOrigin(Uri const &u1, Uri const &u2) noexcept { - return u1.SchemeName() == u2.SchemeName() && u1.Host() == u2.Host() && u1.Port() == u2.Port(); + return (u1 && u2) && u1.SchemeName() == u2.SchemeName() && u1.Host() == u2.Host() && u1.Port() == u2.Port(); } /*static*/ bool OriginPolicyHttpFilter::IsSimpleCorsRequest(HttpRequestMessage const &request) noexcept { @@ -374,7 +371,7 @@ bool OriginPolicyHttpFilter::ConstWcharComparer::operator()(const wchar_t *a, co } } -OriginPolicyHttpFilter::OriginPolicyHttpFilter(IHttpFilter &&innerFilter) : m_innerFilter{std::move(innerFilter)} {} +OriginPolicyHttpFilter::OriginPolicyHttpFilter(IHttpFilter const &innerFilter) : m_innerFilter{innerFilter} {} OriginPolicyHttpFilter::OriginPolicyHttpFilter() : OriginPolicyHttpFilter(winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter{}) {} @@ -443,21 +440,24 @@ OriginPolicy OriginPolicyHttpFilter::ValidateRequest(HttpRequestMessage const &r void OriginPolicyHttpFilter::ValidateAllowOrigin( hstring const &allowedOrigin, hstring const &allowCredentials, - IInspectable const &iRequestArgs) const { + IMap props) const { // 4.10.1-2 - null allow origin if (L"null" == allowedOrigin) throw hresult_error{ E_INVALIDARG, L"Response header Access-Control-Allow-Origin has a value of [null] which differs from the supplied origin"}; - bool withCredentials = iRequestArgs.as()->WithCredentials; + bool withCredentials = props.Lookup(L"RequestArgs").as()->WithCredentials; // 4.10.3 - valid wild card allow origin if (!withCredentials && L"*" == allowedOrigin) return; // We assume the source (request) origin is not "*", "null", or empty string. Valid URI is expected // 4.10.4 - Mismatched allow origin - if (allowedOrigin.empty() || !IsSameOrigin(s_origin, Uri{allowedOrigin})) { + auto taintedOriginProp = props.TryLookup(L"TaintedOrigin"); + auto taintedOrigin = taintedOriginProp && winrt::unbox_value(taintedOriginProp); + auto origin = taintedOrigin ? nullptr : s_origin; + if (allowedOrigin.empty() || !IsSameOrigin(origin, Uri{allowedOrigin})) { hstring errorMessage; if (allowedOrigin.empty()) errorMessage = L"No valid origin in response"; @@ -511,14 +511,14 @@ void OriginPolicyHttpFilter::ValidatePreflightResponse( auto controlValues = ExtractAccessControlValues(response.Headers()); - auto iRequestArgs = request.Properties().Lookup(L"RequestArgs"); + auto props = request.Properties(); // Check if the origin is allowed in conjuction with the withCredentials flag // CORS preflight should always exclude credentials although the subsequent CORS request may include credentials. - ValidateAllowOrigin(controlValues.AllowedOrigin, controlValues.AllowedCredentials, iRequestArgs); + ValidateAllowOrigin(controlValues.AllowedOrigin, controlValues.AllowedCredentials, props); // See https://fetch.spec.whatwg.org/#cors-preflight-fetch, section 4.8.7.5 // Check if the request method is allowed - bool withCredentials = iRequestArgs.as()->WithCredentials; + bool withCredentials = props.Lookup(L"RequestArgs").as()->WithCredentials; bool requestMethodAllowed = false; for (const auto &method : controlValues.AllowedMethods) { if (L"*" == method) { @@ -579,8 +579,8 @@ void OriginPolicyHttpFilter::ValidateResponse(HttpResponseMessage const &respons if (originPolicy == OriginPolicy::SimpleCrossOriginResourceSharing || originPolicy == OriginPolicy::CrossOriginResourceSharing) { auto controlValues = ExtractAccessControlValues(response.Headers()); - auto withCredentials = - response.RequestMessage().Properties().Lookup(L"RequestArgs").try_as()->WithCredentials; + auto props = response.RequestMessage().Properties(); + auto withCredentials = props.Lookup(L"RequestArgs").try_as()->WithCredentials; if (GetRuntimeOptionBool("Http.StrictOriginCheckSimpleCors") && originPolicy == OriginPolicy::SimpleCrossOriginResourceSharing) { @@ -595,8 +595,7 @@ void OriginPolicyHttpFilter::ValidateResponse(HttpResponseMessage const &respons throw hresult_error{E_INVALIDARG, L"The server does not support CORS or the origin is not allowed"}; } } else { - auto iRequestArgs = response.RequestMessage().Properties().Lookup(L"RequestArgs"); - ValidateAllowOrigin(controlValues.AllowedOrigin, controlValues.AllowedCredentials, iRequestArgs); + ValidateAllowOrigin(controlValues.AllowedOrigin, controlValues.AllowedCredentials, props); } if (originPolicy == OriginPolicy::SimpleCrossOriginResourceSharing) { @@ -679,6 +678,38 @@ ResponseOperation OriginPolicyHttpFilter::SendPreflightAsync(HttpRequestMessage co_return {co_await m_innerFilter.SendRequestAsync(preflightRequest)}; } +#pragma region IRedirectEventSource + +bool OriginPolicyHttpFilter::OnRedirecting( + HttpRequestMessage const &request, + HttpResponseMessage const &response) noexcept { + // Consider the following scenario. + // User signs in to http://a.com and visits a page that makes CORS request to http://b.com with origin=http://a.com. + // Http://b.com reponds with a redirect to http://a.com. The browser follows the redirect to http://a.com with + // origin=http://a.com. Since the origin matches the URL, the request is authorized at http://a.com, but it actually + // allows http://b.com to bypass the CORS check at http://a.com since the redirected URL is from http://b.com. + if (!IsSameOrigin(response.Headers().Location(), request.RequestUri()) && + !IsSameOrigin(s_origin, request.RequestUri())) { + // By masking the origin field in the request header, we make it impossible for the server to set a single value for + // the access-control-allow-origin header. It means, the only way to support redirect is that server allows access + // from all sites through wildcard. + request.Headers().Insert(L"Origin", L"null"); + + auto props = request.Properties(); + // Look for 'RequestArgs' key to ensure we are redirecting the main request. + if (auto iReqArgs = props.TryLookup(L"RequestArgs")) { + props.Insert(L"TaintedOrigin", winrt::box_value(true)); + } else { + // Abort redirection if the request is either preflight or extraneous. + return false; + } + } + + return true; +} + +#pragma endregion IRedirectEventSource + #pragma region IHttpFilter ResponseOperation OriginPolicyHttpFilter::SendRequestAsync(HttpRequestMessage const &request) { diff --git a/vnext/Shared/Networking/OriginPolicyHttpFilter.h b/vnext/Shared/Networking/OriginPolicyHttpFilter.h index e0e6e68b6b8..49426d62eab 100644 --- a/vnext/Shared/Networking/OriginPolicyHttpFilter.h +++ b/vnext/Shared/Networking/OriginPolicyHttpFilter.h @@ -3,11 +3,14 @@ #pragma once +#include "IRedirectEventSource.h" #include "OriginPolicy.h" // Windows API +#include #include #include +#include #include // Standard Library @@ -16,7 +19,8 @@ namespace Microsoft::React::Networking { class OriginPolicyHttpFilter - : public winrt::implements { + : public winrt:: + implements { public: struct ConstWcharComparer { bool operator()(const wchar_t *, const wchar_t *) const; @@ -75,7 +79,7 @@ class OriginPolicyHttpFilter winrt::Windows::Web::Http::HttpResponseMessage const &response, bool removeAll); - OriginPolicyHttpFilter(winrt::Windows::Web::Http::Filters::IHttpFilter &&innerFilter); + OriginPolicyHttpFilter(winrt::Windows::Web::Http::Filters::IHttpFilter const &innerFilter); OriginPolicyHttpFilter(); @@ -92,13 +96,22 @@ class OriginPolicyHttpFilter void ValidateAllowOrigin( winrt::hstring const &allowedOrigin, winrt::hstring const &allowCredentials, - winrt::Windows::Foundation::IInspectable const &iArgs) const; + winrt::Windows::Foundation::Collections::IMap props) + const; winrt::Windows::Foundation::IAsyncOperationWithProgress< winrt::Windows::Web::Http::HttpResponseMessage, winrt::Windows::Web::Http::HttpProgress> SendPreflightAsync(winrt::Windows::Web::Http::HttpRequestMessage const &request) const; +#pragma region IRedirectEventSource + + bool OnRedirecting( + winrt::Windows::Web::Http::HttpRequestMessage const &request, + winrt::Windows::Web::Http::HttpResponseMessage const &response) noexcept override; + +#pragma endregion IRedirectEventSource + #pragma region IHttpFilter winrt::Windows::Foundation::IAsyncOperationWithProgress< diff --git a/vnext/Shared/Networking/RedirectHttpFilter.cpp b/vnext/Shared/Networking/RedirectHttpFilter.cpp new file mode 100644 index 00000000000..a5248e031f8 --- /dev/null +++ b/vnext/Shared/Networking/RedirectHttpFilter.cpp @@ -0,0 +1,283 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#undef WINRT_LEAN_AND_MEAN + +#include "RedirectHttpFilter.h" + +#include "WinRTTypes.h" + +// Windows API +#include +#include +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#include +#else +#define INTERNET_ERROR_BASE 12000 +#define ERROR_HTTP_REDIRECT_FAILED (INTERNET_ERROR_BASE + 156) +#endif + +namespace { +constexpr size_t DefaultMaxRedirects = 20; +} // namespace + +using winrt::Windows::Foundation::Uri; +using winrt::Windows::Foundation::Collections::IVector; +using winrt::Windows::Security::Credentials::PasswordCredential; +using winrt::Windows::Security::Cryptography::Certificates::Certificate; +using winrt::Windows::Security::Cryptography::Certificates::ChainValidationResult; +using winrt::Windows::Web::Http::HttpMethod; +using winrt::Windows::Web::Http::HttpRequestMessage; +using winrt::Windows::Web::Http::HttpResponseMessage; +using winrt::Windows::Web::Http::HttpStatusCode; +using winrt::Windows::Web::Http::Filters::IHttpBaseProtocolFilter; +using winrt::Windows::Web::Http::Filters::IHttpFilter; + +namespace Microsoft::React::Networking { + +#pragma region RedirectHttpFilter + +RedirectHttpFilter::RedirectHttpFilter( + size_t maxRedirects, + IHttpFilter &&innerFilter, + IHttpFilter &&innerFilterWithNoCredentials) noexcept + : m_maximumRedirects{maxRedirects}, + m_innerFilter{std::move(innerFilter)}, + m_innerFilterWithNoCredentials{std::move(innerFilterWithNoCredentials)} { + // Prevent automatic redirections. + if (auto baseFilter = m_innerFilter.try_as()) { + baseFilter.AllowAutoRedirect(false); + baseFilter.AllowUI(false); + } + if (auto baseFilter = m_innerFilterWithNoCredentials.try_as()) { + baseFilter.AllowAutoRedirect(false); + baseFilter.AllowUI(false); + } +} + +RedirectHttpFilter::RedirectHttpFilter(IHttpFilter &&innerFilter, IHttpFilter &&innerFilterWithNoCredentials) noexcept + : RedirectHttpFilter(DefaultMaxRedirects, std::move(innerFilter), std::move(innerFilterWithNoCredentials)) {} + +RedirectHttpFilter::RedirectHttpFilter() noexcept + : RedirectHttpFilter( + winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter{}, + winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter{}) {} + +void RedirectHttpFilter::SetRequestFactory(std::weak_ptr factory) noexcept { + m_requestFactory = factory; +} + +void RedirectHttpFilter::SetRedirectSource( + winrt::com_ptr const &eventSrc) noexcept { + m_redirEventSrc = eventSrc; +} + +#pragma region IHttpBaseProtocolFilter + +bool RedirectHttpFilter::AllowAutoRedirect() const { + return m_allowAutoRedirect; +} + +void RedirectHttpFilter::AllowAutoRedirect(bool value) { + m_allowAutoRedirect = value; +} + +bool RedirectHttpFilter::AllowUI() const { + return false; +} +void RedirectHttpFilter::AllowUI(bool /*value*/) const { + throw winrt::hresult_error{HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)}; +} + +bool RedirectHttpFilter::AutomaticDecompression() const { + if (auto baseFilter = m_innerFilter.try_as()) { + return baseFilter.AutomaticDecompression(); + } + + return false; +} +void RedirectHttpFilter::AutomaticDecompression(bool value) const { + if (auto baseFilter = m_innerFilter.try_as()) { + baseFilter.AutomaticDecompression(value); + } +} + +winrt::Windows::Web::Http::Filters::HttpCacheControl RedirectHttpFilter::CacheControl() const { + if (auto baseFilter = m_innerFilter.try_as()) { + return baseFilter.CacheControl(); + } + + return nullptr; +} + +winrt::Windows::Web::Http::HttpCookieManager RedirectHttpFilter::CookieManager() const { + if (auto baseFilter = m_innerFilter.try_as()) { + return baseFilter.CookieManager(); + } + + return nullptr; +} + +Certificate RedirectHttpFilter::ClientCertificate() const { + if (auto baseFilter = m_innerFilter.try_as()) { + return baseFilter.ClientCertificate(); + } + + return nullptr; +} +void RedirectHttpFilter::ClientCertificate(Certificate const &value) const { + if (auto baseFilter = m_innerFilter.try_as()) { + baseFilter.ClientCertificate(value); + } +} + +IVector RedirectHttpFilter::IgnorableServerCertificateErrors() const { + if (auto baseFilter = m_innerFilter.try_as()) { + return baseFilter.IgnorableServerCertificateErrors(); + } + + return nullptr; +} + +uint32_t RedirectHttpFilter::MaxConnectionsPerServer() const { + if (auto baseFilter = m_innerFilter.try_as()) { + return baseFilter.MaxConnectionsPerServer(); + } + + return 0; +} +void RedirectHttpFilter::MaxConnectionsPerServer(uint32_t value) const { + if (auto baseFilter = m_innerFilter.try_as()) { + baseFilter.MaxConnectionsPerServer(value); + } +} + +PasswordCredential RedirectHttpFilter::ProxyCredential() const { + if (auto baseFilter = m_innerFilter.try_as()) { + return baseFilter.ProxyCredential(); + } + + return nullptr; +} +void RedirectHttpFilter::ProxyCredential(PasswordCredential const &value) const { + if (auto baseFilter = m_innerFilter.try_as()) { + baseFilter.ProxyCredential(value); + } +} + +PasswordCredential RedirectHttpFilter::ServerCredential() const { + if (auto baseFilter = m_innerFilter.try_as()) { + return baseFilter.ServerCredential(); + } + + return nullptr; +} +void RedirectHttpFilter::ServerCredential(PasswordCredential const &value) const { + if (auto baseFilter = m_innerFilter.try_as()) { + baseFilter.ServerCredential(value); + } +} + +bool RedirectHttpFilter::UseProxy() const { + if (auto baseFilter = m_innerFilter.try_as()) { + return baseFilter.UseProxy(); + } + + return false; +} +void RedirectHttpFilter::UseProxy(bool value) const { + if (auto baseFilter = m_innerFilter.try_as()) { + baseFilter.UseProxy(value); + } +} + +#pragma endregion IHttpBaseProtocolFilter + +#pragma region IHttpFilter + +/// +/// See https://github.com/dotnet/corefx/pull/22702 +ResponseOperation RedirectHttpFilter::SendRequestAsync(HttpRequestMessage const &request) { + size_t redirectCount = 0; + HttpMethod method{nullptr}; + HttpResponseMessage response{nullptr}; + + auto coRequest = request; + auto coAllowAutoRedirect = m_allowAutoRedirect; + auto coMaxRedirects = m_maximumRedirects; + auto coRequestFactory = m_requestFactory; + auto coEventSrc = m_redirEventSrc; + + method = coRequest.Method(); + + do { + // Send subsequent requests through the filter that doesn't have the credentials included in the first request + response = + co_await (redirectCount > 0 ? m_innerFilterWithNoCredentials : m_innerFilter).SendRequestAsync(coRequest); + + // Stop redirecting when a non-redirect status is responded. + if (response.StatusCode() != HttpStatusCode::MultipleChoices && + response.StatusCode() != HttpStatusCode::MovedPermanently && + response.StatusCode() != HttpStatusCode::Found && // Redirect + response.StatusCode() != HttpStatusCode::SeeOther && // RedirectMethod + response.StatusCode() != HttpStatusCode::TemporaryRedirect && // RedirectKeepVerb + response.StatusCode() != HttpStatusCode::PermanentRedirect) { + break; + } + + redirectCount++; + if (redirectCount > coMaxRedirects) { + throw winrt::hresult_error{HRESULT_FROM_WIN32(ERROR_HTTP_REDIRECT_FAILED), L"Too many redirects"}; + } + + // Call event source's OnRedirecting before modifying request parameters. + if (coEventSrc && !coEventSrc->OnRedirecting(coRequest, response)) { + break; + } + + if (auto requestFactory = coRequestFactory.lock()) { + coRequest = + co_await requestFactory->CreateRequest(HttpMethod{method}, coRequest.RequestUri(), coRequest.Properties()); + + if (!coRequest) { + throw winrt::hresult_error{E_INVALIDARG, L"Invalid request handle"}; + } + } + + auto redirectUri = Uri{response.Headers().Location().AbsoluteUri()}; + if (!redirectUri) { + break; + } + + if (redirectUri.SchemeName() != L"http" && redirectUri.SchemeName() != L"https") { + break; + } + + // Do not "downgrade" from HTTPS to HTTP + if (coRequest.RequestUri().SchemeName() == L"https" && redirectUri.SchemeName() == L"http") { + break; + } + + /// See https://github.com/dotnet/corefx/blob/v3.1.28/src/System.Net.Http/src/uap/System/Net/HttpClientHandler.cs + // Follow HTTP RFC 7231 rules. In general, 3xx responses + // except for 307 and 308 will keep verb except POST becomes GET. + // 307 and 308 responses have all verbs stay the same. + // https://tools.ietf.org/html/rfc7231#section-6.4 + if (response.StatusCode() != HttpStatusCode::TemporaryRedirect && + response.StatusCode() != HttpStatusCode::PermanentRedirect && method != HttpMethod::Post()) { + method = HttpMethod::Get(); + } + + coRequest.RequestUri(redirectUri); + } while (coAllowAutoRedirect); + + response.RequestMessage(coRequest); + + co_return response; +} + +#pragma endregion IHttpFilter + +#pragma endregion RedirectHttpFilter + +} // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/RedirectHttpFilter.h b/vnext/Shared/Networking/RedirectHttpFilter.h new file mode 100644 index 00000000000..949c90a8c37 --- /dev/null +++ b/vnext/Shared/Networking/RedirectHttpFilter.h @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include "IRedirectEventSource.h" +#include "IWinRTHttpRequestFactory.h" + +// Windows API +#include +#include +#include +#include +#include + +namespace Microsoft::React::Networking { + +class RedirectHttpFilter : public winrt::implements< + RedirectHttpFilter, + winrt::Windows::Web::Http::Filters::IHttpFilter, + winrt::Windows::Web::Http::Filters::IHttpBaseProtocolFilter> { + // See + // https://github.com/dotnet/corefx/pull/22702/files#diff-53f0c1940c6bec8054a95caac33680306aa6ab13ac48c9a8c9df013d3bc29d15R30 + // We need two different WinRT filters because we need to remove credentials during redirection requests + // and WinRT doesn't allow changing the filter properties after the first request. + winrt::Windows::Web::Http::Filters::IHttpFilter m_innerFilter; + winrt::Windows::Web::Http::Filters::IHttpFilter m_innerFilterWithNoCredentials; + + std::weak_ptr m_requestFactory; + winrt::com_ptr m_redirEventSrc; + bool m_allowAutoRedirect{true}; + size_t m_maximumRedirects; + + public: + RedirectHttpFilter( + size_t maxRedirects, + winrt::Windows::Web::Http::Filters::IHttpFilter &&innerFilter, + winrt::Windows::Web::Http::Filters::IHttpFilter &&innerFilterWithNoCredentials) noexcept; + + RedirectHttpFilter( + winrt::Windows::Web::Http::Filters::IHttpFilter &&innerFilter, + winrt::Windows::Web::Http::Filters::IHttpFilter &&innerFilterWithNoCredentials) noexcept; + + RedirectHttpFilter() noexcept; + + void SetRequestFactory(std::weak_ptr factory) noexcept; + + void SetRedirectSource(winrt::com_ptr const &eventSrc) noexcept; + +#pragma region IHttpFilter + + winrt::Windows::Foundation::IAsyncOperationWithProgress< + winrt::Windows::Web::Http::HttpResponseMessage, + winrt::Windows::Web::Http::HttpProgress> + SendRequestAsync(winrt::Windows::Web::Http::HttpRequestMessage const &request); + +#pragma endregion IHttpFilter + +#pragma region IHttpBaseProtocolFilter + + bool AllowAutoRedirect() const; + void AllowAutoRedirect(bool value); + + bool AllowUI() const; + void AllowUI(bool value) const; + + bool AutomaticDecompression() const; + void AutomaticDecompression(bool value) const; + + winrt::Windows::Web::Http::Filters::HttpCacheControl CacheControl() const; + + winrt::Windows::Web::Http::HttpCookieManager CookieManager() const; + + winrt::Windows::Security::Cryptography::Certificates::Certificate ClientCertificate() const; + void ClientCertificate(winrt::Windows::Security::Cryptography::Certificates::Certificate const &value) const; + + winrt::Windows::Foundation::Collections::IVector< + winrt::Windows::Security::Cryptography::Certificates::ChainValidationResult> + IgnorableServerCertificateErrors() const; + + uint32_t MaxConnectionsPerServer() const; + void MaxConnectionsPerServer(uint32_t value) const; + + winrt::Windows::Security::Credentials::PasswordCredential ProxyCredential() const; + void ProxyCredential(winrt::Windows::Security::Credentials::PasswordCredential const &value) const; + + winrt::Windows::Security::Credentials::PasswordCredential ServerCredential() const; + void ServerCredential(winrt::Windows::Security::Credentials::PasswordCredential const &value) const; + + bool UseProxy() const; + void UseProxy(bool value) const; + +#pragma endregion IHttpBaseProtocolFilter +}; + +} // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/WinRTHttpResource.cpp b/vnext/Shared/Networking/WinRTHttpResource.cpp index f6c84eb1426..56e9899531f 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.cpp +++ b/vnext/Shared/Networking/WinRTHttpResource.cpp @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#undef WINRT_LEAN_AND_MEAN + #include "WinRTHttpResource.h" #include @@ -8,7 +10,9 @@ #include #include #include +#include "IRedirectEventSource.h" #include "OriginPolicyHttpFilter.h" +#include "RedirectHttpFilter.h" // Boost Libraries #include @@ -31,7 +35,7 @@ using std::weak_ptr; using winrt::fire_and_forget; using winrt::hresult_error; using winrt::to_hstring; -using winrt::to_string; +using winrt::Windows::Foundation::IAsyncOperation; using winrt::Windows::Foundation::IInspectable; using winrt::Windows::Foundation::Uri; using winrt::Windows::Security::Cryptography::CryptographicBuffer; @@ -55,6 +59,138 @@ WinRTHttpResource::WinRTHttpResource(IHttpClient &&client) noexcept : m_client{s WinRTHttpResource::WinRTHttpResource() noexcept : WinRTHttpResource(winrt::Windows::Web::Http::HttpClient{}) {} +#pragma region IWinRTHttpRequestFactory + +IAsyncOperation WinRTHttpResource::CreateRequest( + HttpMethod &&method, + Uri &&uri, + winrt::Windows::Foundation::Collections::IMap props) noexcept /*override*/ { + auto request = HttpRequestMessage{std::move(method), std::move(uri)}; + for (auto prop : props) { + request.Properties().Insert(prop.Key(), prop.Value()); + } + + auto iReqArgs = request.Properties().Lookup(L"RequestArgs"); + auto reqArgs = iReqArgs.as(); + auto self = shared_from_this(); + + HttpMediaTypeHeaderValue contentType{nullptr}; + string contentEncoding; + string contentLength; + + // Headers are generally case-insensitive + // https://www.ietf.org/rfc/rfc2616.txt section 4.2 + for (auto &header : reqArgs->Headers) { + if (boost::iequals(header.first.c_str(), "Content-Type")) { + bool success = HttpMediaTypeHeaderValue::TryParse(to_hstring(header.second), contentType); + if (!success) { + if (self->m_onError) { + self->m_onError(reqArgs->RequestId, "Failed to parse Content-Type", false); + } + co_return nullptr; + } + } else if (boost::iequals(header.first.c_str(), "Content-Encoding")) { + contentEncoding = header.second; + } else if (boost::iequals(header.first.c_str(), "Content-Length")) { + contentLength = header.second; + } else if (boost::iequals(header.first.c_str(), "Authorization")) { + bool success = request.Headers().TryAppendWithoutValidation(to_hstring(header.first), to_hstring(header.second)); + if (!success) { + if (self->m_onError) { + self->m_onError(reqArgs->RequestId, "Failed to append Authorization", false); + } + co_return nullptr; + } + } else { + try { + request.Headers().Append(to_hstring(header.first), to_hstring(header.second)); + } catch (hresult_error const &e) { + if (self->m_onError) { + self->m_onError(reqArgs->RequestId, Utilities::HResultToString(e), false); + } + co_return nullptr; + } + } + } + + // Initialize content + IHttpContent content{nullptr}; + auto &data = reqArgs->Data; + if (!data.isNull()) { + auto bodyHandler = self->m_requestBodyHandler.lock(); + if (bodyHandler && bodyHandler->Supports(data)) { + auto contentTypeString = contentType ? winrt::to_string(contentType.ToString()) : ""; + dynamic blob; + try { + blob = bodyHandler->ToRequestBody(data, contentTypeString); + } catch (const std::invalid_argument &e) { + if (self->m_onError) { + self->m_onError(reqArgs->RequestId, e.what(), false); + } + co_return nullptr; + } + auto bytes = blob["bytes"]; + auto byteVector = vector(bytes.size()); + for (auto &byte : bytes) { + byteVector.push_back(static_cast(byte.asInt())); + } + 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())); + content = HttpBufferContent{std::move(buffer)}; + } else if (!data["uri"].empty()) { + 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["form"].empty()) { + // #9535 - HTTP form data support + // winrt::Windows::Web::Http::HttpMultipartFormDataContent() + } + } + + // Attach content headers + if (content != nullptr) { + if (contentType) { + content.Headers().ContentType(contentType); + } + if (!contentEncoding.empty()) { + if (!content.Headers().ContentEncoding().TryParseAdd(to_hstring(contentEncoding))) { + if (self->m_onError) + self->m_onError(reqArgs->RequestId, "Failed to parse Content-Encoding", false); + + co_return nullptr; + } + } + + if (!contentLength.empty()) { + try { + const auto contentLengthHeader = std::stol(contentLength); + content.Headers().ContentLength(contentLengthHeader); + } catch (const std::invalid_argument &e) { + if (self->m_onError) + self->m_onError(reqArgs->RequestId, e.what() + string{" ["} + contentLength + "]", false); + + co_return nullptr; + } catch (const std::out_of_range &e) { + if (self->m_onError) + self->m_onError(reqArgs->RequestId, e.what() + string{" ["} + contentLength + "]", false); + + co_return nullptr; + } + } + + request.Content(content); + } + + co_return request; +} + +#pragma endregion IWinRTHttpRequestFactory + #pragma region IHttpResource void WinRTHttpResource::SendRequest( @@ -78,29 +214,28 @@ void WinRTHttpResource::SendRequest( try { HttpMethod httpMethod{to_hstring(std::move(method))}; Uri uri{to_hstring(std::move(url))}; - HttpRequestMessage request{httpMethod, uri}; - - auto args = winrt::make(); - auto concreteArgs = args.as(); - concreteArgs->RequestId = requestId; - concreteArgs->Headers = std::move(headers); - concreteArgs->Data = std::move(data); - concreteArgs->IncrementalUpdates = useIncrementalUpdates; - concreteArgs->WithCredentials = withCredentials; - concreteArgs->ResponseType = std::move(responseType); - concreteArgs->Timeout = timeout; - - PerformSendRequest(std::move(request), args); + + auto iReqArgs = winrt::make(); + auto reqArgs = iReqArgs.as(); + reqArgs->RequestId = requestId; + reqArgs->Headers = std::move(headers); + reqArgs->Data = std::move(data); + reqArgs->IncrementalUpdates = useIncrementalUpdates; + reqArgs->WithCredentials = withCredentials; + reqArgs->ResponseType = std::move(responseType); + reqArgs->Timeout = timeout; + + PerformSendRequest(std::move(httpMethod), std::move(uri), iReqArgs); } catch (std::exception const &e) { if (m_onError) { - m_onError(requestId, e.what()); + m_onError(requestId, e.what(), false); } } catch (hresult_error const &e) { if (m_onError) { - m_onError(requestId, Utilities::HResultToString(e)); + m_onError(requestId, Utilities::HResultToString(e), false); } } catch (...) { - m_onError(requestId, "Unidentified error sending HTTP request"); + m_onError(requestId, "Unidentified error sending HTTP request", false); } } @@ -119,7 +254,7 @@ void WinRTHttpResource::AbortRequest(int64_t requestId) noexcept /*override*/ { try { request.Cancel(); } catch (hresult_error const &e) { - m_onError(requestId, Utilities::HResultToString(e)); + m_onError(requestId, Utilities::HResultToString(e), false); } } @@ -148,7 +283,8 @@ void WinRTHttpResource::SetOnData(function &&handler) noexcept +void WinRTHttpResource::SetOnError( + function &&handler) noexcept /*override*/ { m_onError = std::move(handler); } @@ -165,146 +301,53 @@ void WinRTHttpResource::UntrackResponse(int64_t requestId) noexcept { m_responses.erase(requestId); } -fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&request, IInspectable const &args) noexcept { +fire_and_forget +WinRTHttpResource::PerformSendRequest(HttpMethod &&method, Uri &&rtUri, IInspectable const &args) noexcept { // Keep references after coroutine suspension. auto self = shared_from_this(); - auto coRequest = std::move(request); auto coArgs = args; - auto coReqArgs = coArgs.as(); + auto reqArgs = coArgs.as(); + auto coMethod = std::move(method); + auto coUri = std::move(rtUri); // Ensure background thread co_await winrt::resume_background(); + auto props = winrt::multi_threaded_map(); + props.Insert(L"RequestArgs", coArgs); + + auto coRequest = co_await CreateRequest(std::move(coMethod), std::move(coUri), props); + if (!coRequest) { + co_return; + } + // If URI handler is available, it takes over request processing. if (auto uriHandler = self->m_uriHandler.lock()) { auto uri = winrt::to_string(coRequest.RequestUri().ToString()); try { - if (uriHandler->Supports(uri, coReqArgs->ResponseType)) { + if (uriHandler->Supports(uri, reqArgs->ResponseType)) { auto blob = uriHandler->Fetch(uri); if (self->m_onDataDynamic && self->m_onRequestSuccess) { - self->m_onDataDynamic(coReqArgs->RequestId, std::move(blob)); - self->m_onRequestSuccess(coReqArgs->RequestId); + self->m_onDataDynamic(reqArgs->RequestId, std::move(blob)); + self->m_onRequestSuccess(reqArgs->RequestId); } co_return; } } catch (const hresult_error &e) { if (self->m_onError) - co_return self->m_onError(coReqArgs->RequestId, Utilities::HResultToString(e)); + co_return self->m_onError(reqArgs->RequestId, Utilities::HResultToString(e), false); } catch (const std::exception &e) { if (self->m_onError) - co_return self->m_onError(coReqArgs->RequestId, e.what()); - } - } - - HttpMediaTypeHeaderValue contentType{nullptr}; - string contentEncoding; - string contentLength; - - // Headers are generally case-insensitive - // https://www.ietf.org/rfc/rfc2616.txt section 4.2 - for (auto &header : coReqArgs->Headers) { - if (boost::iequals(header.first.c_str(), "Content-Type")) { - bool success = HttpMediaTypeHeaderValue::TryParse(to_hstring(header.second), contentType); - if (!success && m_onError) { - co_return m_onError(coReqArgs->RequestId, "Failed to parse Content-Type"); - } - } else if (boost::iequals(header.first.c_str(), "Content-Encoding")) { - contentEncoding = header.second; - } else if (boost::iequals(header.first.c_str(), "Content-Length")) { - contentLength = header.second; - } else if (boost::iequals(header.first.c_str(), "Authorization")) { - bool success = - coRequest.Headers().TryAppendWithoutValidation(to_hstring(header.first), to_hstring(header.second)); - if (!success && m_onError) { - co_return m_onError(coReqArgs->RequestId, "Failed to append Authorization"); - } - } else if (boost::iequals(header.first.c_str(), "User-Agent")) { - bool success = - coRequest.Headers().TryAppendWithoutValidation(to_hstring(header.first), to_hstring(header.second)); - if (!success && m_onError) { - co_return m_onError(coReqArgs->RequestId, "Failed to append User-Agent"); - } - } else { - try { - coRequest.Headers().Append(to_hstring(header.first), to_hstring(header.second)); - } catch (hresult_error const &e) { - if (self->m_onError) { - co_return self->m_onError(coReqArgs->RequestId, Utilities::HResultToString(e)); - } - } - } - } - - IHttpContent content{nullptr}; - auto &data = coReqArgs->Data; - if (!data.isNull()) { - auto bodyHandler = self->m_requestBodyHandler.lock(); - if (bodyHandler && bodyHandler->Supports(data)) { - auto contentTypeString = contentType ? winrt::to_string(contentType.ToString()) : ""; - dynamic blob; - try { - blob = bodyHandler->ToRequestBody(data, contentTypeString); - } catch (const std::invalid_argument &e) { - if (self->m_onError) { - self->m_onError(coReqArgs->RequestId, e.what()); - } - co_return; - } - auto bytes = blob["bytes"]; - auto byteVector = vector(bytes.size()); - for (auto &byte : bytes) { - byteVector.push_back(static_cast(byte.asInt())); - } - auto view = winrt::array_view{byteVector}; - auto buffer = CryptographicBuffer::CreateFromByteArray(view); - content = HttpBufferContent{std::move(buffer)}; - } else if (!data["string"].empty()) { - content = HttpStringContent{to_hstring(data["string"].asString())}; - } else if (!data["base64"].empty()) { - 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())}); - auto stream = co_await file.OpenReadAsync(); - content = HttpStreamContent{std::move(stream)}; - } else if (!data["form"].empty()) { - // #9535 - HTTP form data support - // winrt::Windows::Web::Http::HttpMultipartFormDataContent() - } else { - // Assume empty request body. - // content = HttpStringContent{L""}; + co_return self->m_onError(reqArgs->RequestId, e.what(), false); } } - if (content != nullptr) { - // Attach content headers - if (contentType) { - content.Headers().ContentType(contentType); - } - if (!contentEncoding.empty()) { - if (!content.Headers().ContentEncoding().TryParseAdd(to_hstring(contentEncoding))) { - if (self->m_onError) - self->m_onError(coReqArgs->RequestId, "Failed to parse Content-Encoding"); - - co_return; - } - } - - if (!contentLength.empty()) { - const auto contentLengthHeader = _atoi64(contentLength.c_str()); - content.Headers().ContentLength(contentLengthHeader); - } - - coRequest.Content(content); - } - try { - coRequest.Properties().Insert(L"RequestArgs", coArgs); auto sendRequestOp = self->m_client.SendRequestAsync(coRequest); - self->TrackResponse(coReqArgs->RequestId, sendRequestOp); + self->TrackResponse(reqArgs->RequestId, sendRequestOp); - if (coReqArgs->Timeout > 0) { + if (reqArgs->Timeout > 0) { // See https://devblogs.microsoft.com/oldnewthing/20220415-00/?p=106486 auto timedOut = std::make_shared(false); auto sendRequestTimeout = [](auto timedOut, auto milliseconds) -> ResponseOperation { @@ -312,7 +355,7 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque co_await winrt::resume_after(winrt::Windows::Foundation::TimeSpan{milliseconds * 10000}); *timedOut = true; co_return nullptr; - }(timedOut, coReqArgs->Timeout); + }(timedOut, reqArgs->Timeout); co_await lessthrow_await_adapter{winrt::when_any(sendRequestOp, sendRequestTimeout)}; @@ -322,9 +365,13 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque if (*timedOut) { if (self->m_onError) { - self->m_onError(coReqArgs->RequestId, Utilities::HResultToString(HRESULT_FROM_WIN32(ERROR_TIMEOUT))); + // TODO: Try to replace with either: + // WININET_E_TIMEOUT + // ERROR_INTERNET_TIMEOUT + // INET_E_CONNECTION_TIMEOUT + self->m_onError(reqArgs->RequestId, Utilities::HResultToString(HRESULT_FROM_WIN32(ERROR_TIMEOUT)), true); } - co_return self->UntrackResponse(coReqArgs->RequestId); + co_return self->UntrackResponse(reqArgs->RequestId); } } else { co_await lessthrow_await_adapter{sendRequestOp}; @@ -333,9 +380,9 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque auto result = sendRequestOp.ErrorCode(); if (result < 0) { if (self->m_onError) { - self->m_onError(coReqArgs->RequestId, Utilities::HResultToString(std::move(result))); + self->m_onError(reqArgs->RequestId, Utilities::HResultToString(std::move(result)), false); } - co_return self->UntrackResponse(coReqArgs->RequestId); + co_return self->UntrackResponse(reqArgs->RequestId); } auto response = sendRequestOp.GetResults(); @@ -354,7 +401,7 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque } self->m_onResponse( - coReqArgs->RequestId, + reqArgs->RequestId, {static_cast(response.StatusCode()), std::move(url), std::move(responseHeaders)}); } } @@ -369,21 +416,21 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque // Let response handler take over, if set if (auto responseHandler = self->m_responseHandler.lock()) { - if (responseHandler->Supports(coReqArgs->ResponseType)) { + if (responseHandler->Supports(reqArgs->ResponseType)) { auto bytes = vector(reader.UnconsumedBufferLength()); reader.ReadBytes(bytes); auto blob = responseHandler->ToResponseData(std::move(bytes)); if (self->m_onDataDynamic && self->m_onRequestSuccess) { - self->m_onDataDynamic(coReqArgs->RequestId, std::move(blob)); - self->m_onRequestSuccess(coReqArgs->RequestId); + self->m_onDataDynamic(reqArgs->RequestId, std::move(blob)); + self->m_onRequestSuccess(reqArgs->RequestId); } co_return; } } - auto isText = coReqArgs->ResponseType == "text"; + auto isText = reqArgs->ResponseType == "text"; if (isText) { reader.UnicodeEncoding(UnicodeEncoding::Utf8); } @@ -406,33 +453,33 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpRequestMessage &&reque buffer = reader.ReadBuffer(length); auto data = CryptographicBuffer::EncodeToBase64String(buffer); - responseData += to_string(std::wstring_view(data)); + responseData += winrt::to_string(std::wstring_view(data)); } } while (length > 0); if (self->m_onData) { - self->m_onData(coReqArgs->RequestId, std::move(responseData)); + self->m_onData(reqArgs->RequestId, std::move(responseData)); } } else { if (self->m_onError) { - self->m_onError(coReqArgs->RequestId, response == nullptr ? "request failed" : "No response content"); + self->m_onError(reqArgs->RequestId, response == nullptr ? "request failed" : "No response content", false); } } } catch (std::exception const &e) { if (self->m_onError) { - self->m_onError(coReqArgs->RequestId, e.what()); + self->m_onError(reqArgs->RequestId, e.what(), false); } } catch (hresult_error const &e) { if (self->m_onError) { - self->m_onError(coReqArgs->RequestId, Utilities::HResultToString(e)); + self->m_onError(reqArgs->RequestId, Utilities::HResultToString(e), false); } } catch (...) { if (self->m_onError) { - self->m_onError(coReqArgs->RequestId, "Unhandled exception during request"); + self->m_onError(reqArgs->RequestId, "Unhandled exception during request", false); } } - self->UntrackResponse(coReqArgs->RequestId); + self->UntrackResponse(reqArgs->RequestId); } // PerformSendRequest #pragma region IHttpModuleProxy @@ -463,19 +510,25 @@ void WinRTHttpResource::AddResponseHandler(shared_ptr response using namespace winrt::Microsoft::ReactNative; using winrt::Windows::Web::Http::HttpClient; - shared_ptr result; + auto redirFilter = winrt::make(); + HttpClient client; if (static_cast(GetRuntimeOptionInt("Http.OriginPolicy")) == OriginPolicy::None) { - result = std::make_shared(); + client = HttpClient{redirFilter}; } else { auto globalOrigin = GetRuntimeOptionString("Http.GlobalOrigin"); OriginPolicyHttpFilter::SetStaticOrigin(std::move(globalOrigin)); - auto opFilter = winrt::make(); - auto client = HttpClient{opFilter}; + auto opFilter = winrt::make(redirFilter); + redirFilter.as()->SetRedirectSource(opFilter.as()); - result = std::make_shared(std::move(client)); + client = HttpClient{opFilter}; } + auto result = std::make_shared(std::move(client)); + + // Allow redirect filter to create requests based on the resource's state + redirFilter.as()->SetRequestFactory(weak_ptr{result}); + // Register resource as HTTP module proxy. if (inspectableProperties) { auto propId = ReactPropertyId>>{L"HttpModule.Proxy"}; diff --git a/vnext/Shared/Networking/WinRTHttpResource.h b/vnext/Shared/Networking/WinRTHttpResource.h index 44680ca3fb8..ef9c4a854ed 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.h +++ b/vnext/Shared/Networking/WinRTHttpResource.h @@ -6,6 +6,7 @@ #include "IHttpResource.h" #include +#include "IWinRTHttpRequestFactory.h" #include "WinRTTypes.h" // Windows API @@ -18,6 +19,7 @@ namespace Microsoft::React::Networking { class WinRTHttpResource : public IHttpResource, public IHttpModuleProxy, + public IWinRTHttpRequestFactory, public std::enable_shared_from_this { winrt::Windows::Web::Http::IHttpClient m_client; std::mutex m_mutex; @@ -27,7 +29,7 @@ namespace Microsoft::React::Networking { std::function m_onResponse; std::function m_onData; std::function m_onDataDynamic; - std::function m_onError; + std::function m_onError; // Used for IHttpModuleProxy std::weak_ptr m_uriHandler; @@ -39,7 +41,8 @@ namespace Microsoft::React::Networking { void UntrackResponse(int64_t requestId) noexcept; winrt::fire_and_forget PerformSendRequest( - winrt::Windows::Web::Http::HttpRequestMessage&& request, + winrt::Windows::Web::Http::HttpMethod&& method, + winrt::Windows::Foundation::Uri&& uri, winrt::Windows::Foundation::IInspectable const& args) noexcept; public: @@ -47,6 +50,16 @@ namespace Microsoft::React::Networking { WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient&& client) noexcept; +#pragma region IWinRTHttpRequestFactory + + winrt::Windows::Foundation::IAsyncOperation CreateRequest( + winrt::Windows::Web::Http::HttpMethod&& method, + winrt::Windows::Foundation::Uri&& uri, + winrt::Windows::Foundation::Collections::IMap + props) noexcept override; + +#pragma endregion IWinRTHttpRequestFactory + #pragma region IHttpResource void SendRequest( @@ -67,8 +80,8 @@ namespace Microsoft::React::Networking { void SetOnResponse(std::function&& handler) noexcept override; void SetOnData(std::function&& handler) noexcept override; void SetOnData(std::function&& handler) noexcept override; - void SetOnError(std::function - && handler) noexcept override; + void SetOnError( + std::function&& handler) noexcept override; #pragma endregion IHttpResource diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index cb02173c0c0..e6a7e68d3ad 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -57,6 +57,7 @@ + @@ -104,9 +105,12 @@ + + + diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 0a9696f3415..797cb198747 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -155,6 +155,9 @@ Source Files\Modules + + Source Files\Networking + @@ -460,6 +463,15 @@ Header Files\Modules + + Header Files\Networking + + + Header Files\Networking + + + Header Files\Networking + diff --git a/vnext/Test/HttpServer.cpp b/vnext/Test/HttpServer.cpp index b88510ac613..d6ca802d3cb 100644 --- a/vnext/Test/HttpServer.cpp +++ b/vnext/Test/HttpServer.cpp @@ -41,14 +41,14 @@ boost::beast::multi_buffer CreateStringResponseBody(string&& content) #pragma region ResponseWrapper -ResponseWrapper::ResponseWrapper(DynamicResponse&& res) - : m_response{ make_shared(std::move(res)) } +ResponseWrapper::ResponseWrapper(DynamicResponse&& response) + : m_response{ make_shared(std::move(response)) } , m_type{ ResponseType::Dynamic } { } -ResponseWrapper::ResponseWrapper(EmptyResponse&& res) - : m_response{ make_shared(std::move(res)) } +ResponseWrapper::ResponseWrapper(EmptyResponse&& response) + : m_response{ make_shared(std::move(response)) } , m_type{ ResponseType::Empty } { } @@ -65,6 +65,24 @@ ResponseWrapper::ResponseWrapper(StringResponse&& response) { } +ResponseWrapper::ResponseWrapper(DynamicResponse const& response) + : m_response{ make_shared(response) } + , m_type{ ResponseType::Dynamic } +{ +} + +ResponseWrapper::ResponseWrapper(EmptyResponse const& response) + : m_response{ make_shared(response) } + , m_type{ ResponseType::Empty } +{ +} + +ResponseWrapper::ResponseWrapper(StringResponse const& response) + : m_response{ make_shared(response) } + , m_type{ ResponseType::String } +{ +} + shared_ptr ResponseWrapper::Response() { return m_response; diff --git a/vnext/Test/HttpServer.h b/vnext/Test/HttpServer.h index 52cceeba433..a3e25b2f9ad 100644 --- a/vnext/Test/HttpServer.h +++ b/vnext/Test/HttpServer.h @@ -39,6 +39,12 @@ class ResponseWrapper ResponseWrapper(StringResponse&& response); + ResponseWrapper(DynamicResponse const& response); + + ResponseWrapper(EmptyResponse const& response); + + ResponseWrapper(StringResponse const& response); + ResponseWrapper(ResponseWrapper&&) = default; std::shared_ptr Response(); From 5f8c7e18c010e9b883a525373ae1fff9fffb652b Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 16:01:40 -0700 Subject: [PATCH 16/23] Change files --- ...ative-windows-b2f23c96-082a-4440-8d14-1d5a0ce3d115.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-b2f23c96-082a-4440-8d14-1d5a0ce3d115.json diff --git a/change/react-native-windows-b2f23c96-082a-4440-8d14-1d5a0ce3d115.json b/change/react-native-windows-b2f23c96-082a-4440-8d14-1d5a0ce3d115.json new file mode 100644 index 00000000000..8d385233d4c --- /dev/null +++ b/change/react-native-windows-b2f23c96-082a-4440-8d14-1d5a0ce3d115.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Implement HTTP redirection (#10534)", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} From 0cf1e76c176c943b2ddeec6112f54bb4f1de4fb3 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 16:32:34 -0700 Subject: [PATCH 17/23] Fix merge errors --- .../Microsoft.ReactNative.Cxx.vcxitems.filters | 1 - vnext/Shared/Modules/WebSocketModule.h | 5 +---- vnext/Shared/Shared.vcxitems.filters | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters index a4c7313ec27..a6085917d3f 100644 --- a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters +++ b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters @@ -30,7 +30,6 @@ JSI - diff --git a/vnext/Shared/Modules/WebSocketModule.h b/vnext/Shared/Modules/WebSocketModule.h index d67878f838c..b637cc182bd 100644 --- a/vnext/Shared/Modules/WebSocketModule.h +++ b/vnext/Shared/Modules/WebSocketModule.h @@ -36,7 +36,7 @@ namespace Microsoft::React { public: enum MethodId { Connect = 0, Close = 1, Send = 2, SendBinary = 3, Ping = 4, SIZE = 5 }; - WebSocketModule(winrt::Windows::Foundation::IInspectable const& inspectablePropertieswinrt::Windows::Foundation::IInspectable const& inspectableProperties); + WebSocketModule(winrt::Windows::Foundation::IInspectable const& inspectableProperties); ~WebSocketModule() noexcept override; @@ -59,9 +59,6 @@ namespace Microsoft::React { // Property bag high level reference. winrt::Windows::Foundation::IInspectable InspectableProps; - - // Property bag high level reference. - winrt::Windows::Foundation::IInspectable InspectableProps; }; #pragma region CxxModule overrides diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 797cb198747..8109f928a8c 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -148,7 +148,6 @@ Source Files\Networking - Source Files\Modules From 9af67516a2696d18480da871ce7b9f25293e2a90 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 16:33:54 -0700 Subject: [PATCH 18/23] clang format --- .../HttpResourceIntegrationTests.cpp | 793 +++++++------ vnext/Shared/Modules/HttpModule.cpp | 124 +- vnext/Shared/Modules/WebSocketModule.cpp | 279 +++-- vnext/Shared/Modules/WebSocketModule.h | 158 +-- .../Shared/Networking/RedirectHttpFilter.cpp | 3 +- vnext/Shared/Networking/WinRTHttpResource.h | 96 +- vnext/Shared/Networking/WinRTTypes.h | 24 +- vnext/Shared/OInstance.cpp | 1049 ++++++++--------- vnext/Shared/OInstance.h | 166 +-- vnext/Shared/RuntimeOptions.cpp | 91 +- vnext/Shared/RuntimeOptions.h | 14 +- 11 files changed, 1387 insertions(+), 1410 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 38d2d0d7592..88c3d921c88 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -36,43 +36,43 @@ using Test::ResponseWrapper; namespace Microsoft::React::Test { - TEST_CLASS(HttpResourceIntegrationTest) { - static uint16_t s_port; +TEST_CLASS (HttpResourceIntegrationTest) { + static uint16_t s_port; + + TEST_METHOD_CLEANUP(MethodCleanup) { + // Bug in WebSocketServer does not correctly release TCP port between test methods. + // Using a different por per test for now. + s_port++; + } + TEST_METHOD(RequestGetSucceeds) { + string url = "http://localhost:" + std::to_string(s_port); + + promise resPromise; + string error; + int statusCode = 0; + + auto server = make_shared(s_port); + server->Callbacks().OnGet = [&resPromise](const DynamicRequest &request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + response.body() = Test::CreateStringResponseBody("some response content"); + + return {std::move(response)}; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&resPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + }); + resource->SetOnData([&resPromise](int64_t, string &&content) { resPromise.set_value(); }); + resource->SetOnError([&resPromise, &error, &server](int64_t, string &&message, bool) { + error = std::move(message); + resPromise.set_value(); - TEST_METHOD_CLEANUP(MethodCleanup) { - // Bug in WebSocketServer does not correctly release TCP port between test methods. - // Using a different por per test for now. - s_port++; - } - TEST_METHOD(RequestGetSucceeds) { - string url = "http://localhost:" + std::to_string(s_port); - - promise resPromise; - string error; - int statusCode = 0; - - auto server = make_shared(s_port); - server->Callbacks().OnGet = [&resPromise](const DynamicRequest& request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - response.body() = Test::CreateStringResponseBody("some response content"); - - return { std::move(response) }; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&resPromise, &statusCode](int64_t, IHttpResource::Response response) { - statusCode = static_cast(response.StatusCode); - }); - resource->SetOnData([&resPromise](int64_t, string&& content) { resPromise.set_value(); }); - resource->SetOnError([&resPromise, &error, &server](int64_t, string&& message, bool) { - error = std::move(message); - resPromise.set_value(); - - server->Stop(); - }); - resource->SendRequest( + server->Stop(); + }); + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -84,51 +84,51 @@ namespace Microsoft::React::Test { false /*withCredentials*/, [](int64_t) {}); - // Synchronize response. - resPromise.get_future().wait(); - server->Stop(); - - Assert::AreEqual({}, error); - Assert::AreEqual(200, statusCode); - } - - TEST_METHOD(RequestGetHeadersSucceeds) { - string url = "http://localhost:" + std::to_string(s_port); - - promise rcPromise; - string error; - IHttpResource::Response response; - - auto server = make_shared(s_port); - server->Callbacks().OnGet = [](const DynamicRequest& request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - - // Response header - response.set(http::field::server, "Microsoft::React::Test::HttpServer"); - // Response content header - response.set(http::field::content_length, "0"); - // Response arbitrary header - response.set("ResponseHeaderName1", "ResponseHeaderValue1"); - - return { std::move(response) }; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&rcPromise, &response](int64_t, IHttpResource::Response callbackResponse) { - response = callbackResponse; - rcPromise.set_value(); - }); - resource->SetOnError([&rcPromise, &error, &server](int64_t, string&& message, bool) { - error = std::move(message); - rcPromise.set_value(); - - server->Abort(); - }); - - //clang-format off - resource->SendRequest( + // Synchronize response. + resPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({}, error); + Assert::AreEqual(200, statusCode); + } + + TEST_METHOD(RequestGetHeadersSucceeds) { + string url = "http://localhost:" + std::to_string(s_port); + + promise rcPromise; + string error; + IHttpResource::Response response; + + auto server = make_shared(s_port); + server->Callbacks().OnGet = [](const DynamicRequest &request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + + // Response header + response.set(http::field::server, "Microsoft::React::Test::HttpServer"); + // Response content header + response.set(http::field::content_length, "0"); + // Response arbitrary header + response.set("ResponseHeaderName1", "ResponseHeaderValue1"); + + return {std::move(response)}; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&rcPromise, &response](int64_t, IHttpResource::Response callbackResponse) { + response = callbackResponse; + rcPromise.set_value(); + }); + resource->SetOnError([&rcPromise, &error, &server](int64_t, string &&message, bool) { + error = std::move(message); + rcPromise.set_value(); + + server->Abort(); + }); + + //clang-format off + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -144,95 +144,91 @@ namespace Microsoft::React::Test { 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); - //clang-format on - - rcPromise.get_future().wait(); - server->Stop(); - - Assert::AreEqual({}, error, L"Error encountered"); - for (auto header : response.Headers) { - if (header.first == "Server") { - Assert::AreEqual({ "Microsoft::React::Test::HttpServer" }, header.second, L"Wrong header"); - } - else if (header.first == "Content-Length") { - Assert::AreEqual({ "0" }, header.second, L"Wrong header"); - } - else if (header.first == "ResponseHeaderName1") { - Assert::AreEqual({ "ResponseHeaderValue1" }, header.second, L"Wrong header"); - } - else { - string message = "Unexpected header: [" + header.first + "]=[" + header.second + "]"; - Assert::Fail(Microsoft::Common::Unicode::Utf8ToUtf16(message).c_str()); - } + //clang-format on + + rcPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({}, error, L"Error encountered"); + for (auto header : response.Headers) { + if (header.first == "Server") { + Assert::AreEqual({"Microsoft::React::Test::HttpServer"}, header.second, L"Wrong header"); + } else if (header.first == "Content-Length") { + Assert::AreEqual({"0"}, header.second, L"Wrong header"); + } else if (header.first == "ResponseHeaderName1") { + Assert::AreEqual({"ResponseHeaderValue1"}, header.second, L"Wrong header"); + } else { + string message = "Unexpected header: [" + header.first + "]=[" + header.second + "]"; + Assert::Fail(Microsoft::Common::Unicode::Utf8ToUtf16(message).c_str()); } } - - TEST_METHOD(RequestGetFails) { - string error; - promise promise; - - auto resource = IHttpResource::Make(); - resource->SetOnError([&error, &promise](int64_t, string&& message, bool) { - error = message; - promise.set_value(); - }); - - resource->SendRequest("GET", "http://nonexistinghost", 0, {}, {}, "text", false, 0, false, [](int64_t) {}); - - promise.get_future().wait(); - - Logger::WriteMessage(error.c_str()); - Assert::AreNotEqual(string{}, error); - } - - TEST_METHOD(RequestOptionsSucceeds) { - string url = "http://localhost:" + std::to_string(s_port); - - promise getResponsePromise; - promise getDataPromise; - promise optionsPromise; - string error; - IHttpResource::Response getResponse; - IHttpResource::Response optionsResponse; - string content; - - auto server = make_shared(s_port); - server->Callbacks().OnOptions = [](const DynamicRequest& request) -> ResponseWrapper { - EmptyResponse response; - response.result(http::status::partial_content); - response.set("PreflightName", "PreflightValue"); - - return { std::move(response) }; - }; - server->Callbacks().OnGet = [](const DynamicRequest& request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - response.body() = Test::CreateStringResponseBody("Response Body"); - - return { std::move(response) }; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&getResponse, &getResponsePromise, &optionsResponse, &optionsPromise]( - int64_t, IHttpResource::Response callbackResponse) { - if (callbackResponse.StatusCode == static_cast(http::status::ok)) { - getResponse = callbackResponse; - getResponsePromise.set_value(); - } - else if (callbackResponse.StatusCode == static_cast(http::status::partial_content)) { - optionsResponse = callbackResponse; - optionsPromise.set_value(); - } - }); - resource->SetOnData([&getDataPromise, &content](int64_t, string&& responseData) { - content = std::move(responseData); - - if (!content.empty()) - getDataPromise.set_value(); - }); - resource->SetOnError( - [&optionsPromise, &getResponsePromise, &getDataPromise, &error, &server](int64_t, string&& message, bool) { + } + + TEST_METHOD(RequestGetFails) { + string error; + promise promise; + + auto resource = IHttpResource::Make(); + resource->SetOnError([&error, &promise](int64_t, string &&message, bool) { + error = message; + promise.set_value(); + }); + + resource->SendRequest("GET", "http://nonexistinghost", 0, {}, {}, "text", false, 0, false, [](int64_t) {}); + + promise.get_future().wait(); + + Logger::WriteMessage(error.c_str()); + Assert::AreNotEqual(string{}, error); + } + + TEST_METHOD(RequestOptionsSucceeds) { + string url = "http://localhost:" + std::to_string(s_port); + + promise getResponsePromise; + promise getDataPromise; + promise optionsPromise; + string error; + IHttpResource::Response getResponse; + IHttpResource::Response optionsResponse; + string content; + + auto server = make_shared(s_port); + server->Callbacks().OnOptions = [](const DynamicRequest &request) -> ResponseWrapper { + EmptyResponse response; + response.result(http::status::partial_content); + response.set("PreflightName", "PreflightValue"); + + return {std::move(response)}; + }; + server->Callbacks().OnGet = [](const DynamicRequest &request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + response.body() = Test::CreateStringResponseBody("Response Body"); + + return {std::move(response)}; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getResponse, &getResponsePromise, &optionsResponse, &optionsPromise]( + int64_t, IHttpResource::Response callbackResponse) { + if (callbackResponse.StatusCode == static_cast(http::status::ok)) { + getResponse = callbackResponse; + getResponsePromise.set_value(); + } else if (callbackResponse.StatusCode == static_cast(http::status::partial_content)) { + optionsResponse = callbackResponse; + optionsPromise.set_value(); + } + }); + resource->SetOnData([&getDataPromise, &content](int64_t, string &&responseData) { + content = std::move(responseData); + + if (!content.empty()) + getDataPromise.set_value(); + }); + resource->SetOnError( + [&optionsPromise, &getResponsePromise, &getDataPromise, &error, &server](int64_t, string &&message, bool) { error = std::move(message); optionsPromise.set_value(); @@ -242,10 +238,10 @@ namespace Microsoft::React::Test { server->Stop(); }); - //clang-format off - resource->SendRequest( + //clang-format off + resource->SendRequest( "OPTIONS", - string{ url }, + string{url}, 0, /*requestId*/ {}, /*headers*/ {}, /*data*/ @@ -254,7 +250,7 @@ namespace Microsoft::React::Test { 1000 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); - resource->SendRequest( + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -265,80 +261,79 @@ namespace Microsoft::React::Test { 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); - //clang-format on - - optionsPromise.get_future().wait(); - getResponsePromise.get_future().wait(); - getDataPromise.get_future().wait(); - server->Stop(); - - Assert::AreEqual({}, error, L"Error encountered"); - Assert::AreEqual(static_cast(1), optionsResponse.Headers.size()); - for (auto header : optionsResponse.Headers) { - if (header.first == "PreflightName") { - Assert::AreEqual({ "PreflightValue" }, header.second, L"Wrong header"); - } - else { - string message = "Unexpected header: [" + header.first + "]=[" + header.second + "]"; - Assert::Fail(Microsoft::Common::Unicode::Utf8ToUtf16(message).c_str()); - } + //clang-format on + + optionsPromise.get_future().wait(); + getResponsePromise.get_future().wait(); + getDataPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({}, error, L"Error encountered"); + Assert::AreEqual(static_cast(1), optionsResponse.Headers.size()); + for (auto header : optionsResponse.Headers) { + if (header.first == "PreflightName") { + Assert::AreEqual({"PreflightValue"}, header.second, L"Wrong header"); + } else { + string message = "Unexpected header: [" + header.first + "]=[" + header.second + "]"; + Assert::Fail(Microsoft::Common::Unicode::Utf8ToUtf16(message).c_str()); } - Assert::AreEqual({ "Response Body" }, content); } - - TEST_METHOD(SimpleRedirectGetSucceeds) { - auto port1 = s_port; - auto port2 = ++s_port; - string url = "http://localhost:" + std::to_string(port1); - - promise responsePromise; - promise contentPromise; - IHttpResource::Response responseResult; - string content; - string error; - - auto server1 = make_shared(port1); - server1->Callbacks().OnGet = [port2](const DynamicRequest& request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::moved_permanently); - response.set(http::field::location, { "http://localhost:" + std::to_string(port2) }); - - return { std::move(response) }; - }; - auto server2 = make_shared(port2); - server2->Callbacks().OnGet = [](const DynamicRequest& request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - response.body() = Test::CreateStringResponseBody("Redirect Content"); - - return { std::move(response) }; - }; - - server1->Start(); - server2->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&responseResult, &responsePromise](int64_t, IHttpResource::Response response) { - if (response.StatusCode == static_cast(http::status::ok)) { - responseResult = response; - responsePromise.set_value(); - } - }); - resource->SetOnData([&contentPromise, &content](int64_t, string&& responseData) { - content = std::move(responseData); - - if (!content.empty()) - contentPromise.set_value(); - }); - resource->SetOnError([&responsePromise, &contentPromise, &error, &server1](int64_t, string&& message, bool) { - error = std::move(message); - + Assert::AreEqual({"Response Body"}, content); + } + + TEST_METHOD(SimpleRedirectGetSucceeds) { + auto port1 = s_port; + auto port2 = ++s_port; + string url = "http://localhost:" + std::to_string(port1); + + promise responsePromise; + promise contentPromise; + IHttpResource::Response responseResult; + string content; + string error; + + auto server1 = make_shared(port1); + server1->Callbacks().OnGet = [port2](const DynamicRequest &request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::moved_permanently); + response.set(http::field::location, {"http://localhost:" + std::to_string(port2)}); + + return {std::move(response)}; + }; + auto server2 = make_shared(port2); + server2->Callbacks().OnGet = [](const DynamicRequest &request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + response.body() = Test::CreateStringResponseBody("Redirect Content"); + + return {std::move(response)}; + }; + + server1->Start(); + server2->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&responseResult, &responsePromise](int64_t, IHttpResource::Response response) { + if (response.StatusCode == static_cast(http::status::ok)) { + responseResult = response; responsePromise.set_value(); + } + }); + resource->SetOnData([&contentPromise, &content](int64_t, string &&responseData) { + content = std::move(responseData); + + if (!content.empty()) contentPromise.set_value(); - }); + }); + resource->SetOnError([&responsePromise, &contentPromise, &error, &server1](int64_t, string &&message, bool) { + error = std::move(message); - //clang-format off - resource->SendRequest( + responsePromise.set_value(); + contentPromise.set_value(); + }); + + //clang-format off + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -349,72 +344,72 @@ namespace Microsoft::React::Test { 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); - //clang-format on - - responsePromise.get_future().wait(); - contentPromise.get_future().wait(); - - server2->Stop(); - server1->Stop(); - - Assert::AreEqual({}, error, L"Error encountered"); - Assert::AreEqual(static_cast(200), responseResult.StatusCode); - Assert::AreEqual({ "Redirect Content" }, content); - } - - TEST_METHOD(SimpleRedirectPatchSucceeds) { - auto port1 = s_port; - auto port2 = ++s_port; - string url = "http://localhost:" + std::to_string(port1); - - promise responsePromise; - promise contentPromise; - IHttpResource::Response responseResult; - string content; - string error; - - auto server1 = make_shared(port1); - server1->Callbacks().OnPatch = [port2](const DynamicRequest& request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::moved_permanently); - response.set(http::field::location, { "http://localhost:" + std::to_string(port2) }); - - return { std::move(response) }; - }; - auto server2 = make_shared(port2); - server2->Callbacks().OnPatch = [](const DynamicRequest& request) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - response.body() = Test::CreateStringResponseBody("Redirect Content"); - - return { std::move(response) }; - }; - - server1->Start(); - server2->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&responseResult, &responsePromise](int64_t, IHttpResource::Response response) { - if (response.StatusCode == static_cast(http::status::ok)) { - responseResult = response; - responsePromise.set_value(); - } - }); - resource->SetOnData([&contentPromise, &content](int64_t, string&& responseData) { - content = std::move(responseData); - - if (!content.empty()) - contentPromise.set_value(); - }); - resource->SetOnError([&responsePromise, &contentPromise, &error, &server1](int64_t, string&& message, bool) { - error = std::move(message); - + //clang-format on + + responsePromise.get_future().wait(); + contentPromise.get_future().wait(); + + server2->Stop(); + server1->Stop(); + + Assert::AreEqual({}, error, L"Error encountered"); + Assert::AreEqual(static_cast(200), responseResult.StatusCode); + Assert::AreEqual({"Redirect Content"}, content); + } + + TEST_METHOD(SimpleRedirectPatchSucceeds) { + auto port1 = s_port; + auto port2 = ++s_port; + string url = "http://localhost:" + std::to_string(port1); + + promise responsePromise; + promise contentPromise; + IHttpResource::Response responseResult; + string content; + string error; + + auto server1 = make_shared(port1); + server1->Callbacks().OnPatch = [port2](const DynamicRequest &request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::moved_permanently); + response.set(http::field::location, {"http://localhost:" + std::to_string(port2)}); + + return {std::move(response)}; + }; + auto server2 = make_shared(port2); + server2->Callbacks().OnPatch = [](const DynamicRequest &request) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + response.body() = Test::CreateStringResponseBody("Redirect Content"); + + return {std::move(response)}; + }; + + server1->Start(); + server2->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&responseResult, &responsePromise](int64_t, IHttpResource::Response response) { + if (response.StatusCode == static_cast(http::status::ok)) { + responseResult = response; responsePromise.set_value(); + } + }); + resource->SetOnData([&contentPromise, &content](int64_t, string &&responseData) { + content = std::move(responseData); + + if (!content.empty()) contentPromise.set_value(); - }); + }); + resource->SetOnError([&responsePromise, &contentPromise, &error, &server1](int64_t, string &&message, bool) { + error = std::move(message); - //clang-format off - resource->SendRequest( + responsePromise.set_value(); + contentPromise.set_value(); + }); + + //clang-format off + resource->SendRequest( "PATCH", std::move(url), 0, /*requestId*/ @@ -425,52 +420,52 @@ namespace Microsoft::React::Test { 0 /*timeout*/, false /*withCredentials*/, [](int64_t) {}); - //clang-format on - - responsePromise.get_future().wait(); - contentPromise.get_future().wait(); - - server2->Stop(); - server1->Stop(); - - Assert::AreEqual({}, error, L"Error encountered"); - Assert::AreEqual(static_cast(200), responseResult.StatusCode); - Assert::AreEqual({ "Redirect Content" }, content); - } - - TEST_METHOD(TimeoutSucceeds) { - auto port = s_port; - string url = "http://localhost:" + std::to_string(port); - - promise getPromise; - string error; - int statusCode = 0; - bool timeoutError = false; - - auto server = std::make_shared(s_port); - server->Callbacks().OnGet = [](const DynamicRequest&) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - - // Hold response to test client timeout - promise timer; - timer.get_future().wait_for(std::chrono::milliseconds(2000)); - - return { std::move(response) }; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { - statusCode = static_cast(response.StatusCode); - getPromise.set_value(); - }); - resource->SetOnError([&getPromise, &error, &timeoutError](int64_t, string&& errorMessage, bool isTimeout) { - error = std::move(errorMessage); - timeoutError = isTimeout; - getPromise.set_value(); - }); - resource->SendRequest( + //clang-format on + + responsePromise.get_future().wait(); + contentPromise.get_future().wait(); + + server2->Stop(); + server1->Stop(); + + Assert::AreEqual({}, error, L"Error encountered"); + Assert::AreEqual(static_cast(200), responseResult.StatusCode); + Assert::AreEqual({"Redirect Content"}, content); + } + + TEST_METHOD(TimeoutSucceeds) { + auto port = s_port; + string url = "http://localhost:" + std::to_string(port); + + promise getPromise; + string error; + int statusCode = 0; + bool timeoutError = false; + + auto server = std::make_shared(s_port); + server->Callbacks().OnGet = [](const DynamicRequest &) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + + // Hold response to test client timeout + promise timer; + timer.get_future().wait_for(std::chrono::milliseconds(2000)); + + return {std::move(response)}; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + getPromise.set_value(); + }); + resource->SetOnError([&getPromise, &error, &timeoutError](int64_t, string &&errorMessage, bool isTimeout) { + error = std::move(errorMessage); + timeoutError = isTimeout; + getPromise.set_value(); + }); + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -482,47 +477,47 @@ namespace Microsoft::React::Test { false, /*withCredentials*/ [](int64_t) {} /*callback*/); - getPromise.get_future().wait(); - server->Stop(); - - Assert::AreEqual({}, error); - Assert::IsFalse(timeoutError); - Assert::AreEqual(200, statusCode); - } - - TEST_METHOD(TimeoutFails) { - auto port = s_port; - string url = "http://localhost:" + std::to_string(port); - - promise getPromise; - string error; - int statusCode = 0; - bool timeoutError = false; - - auto server = std::make_shared(s_port); - server->Callbacks().OnGet = [](const DynamicRequest&) -> ResponseWrapper { - DynamicResponse response; - response.result(http::status::ok); - - // Hold response to test client timeout - promise timer; - timer.get_future().wait_for(std::chrono::milliseconds(4000)); - - return { std::move(response) }; - }; - server->Start(); - - auto resource = IHttpResource::Make(); - resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { - statusCode = static_cast(response.StatusCode); - getPromise.set_value(); - }); - resource->SetOnError([&getPromise, &error, &timeoutError](int64_t, string&& errorMessage, bool isTimeout) { - error = std::move(errorMessage); - timeoutError = isTimeout; - getPromise.set_value(); - }); - resource->SendRequest( + getPromise.get_future().wait(); + server->Stop(); + + Assert::AreEqual({}, error); + Assert::IsFalse(timeoutError); + Assert::AreEqual(200, statusCode); + } + + TEST_METHOD(TimeoutFails) { + auto port = s_port; + string url = "http://localhost:" + std::to_string(port); + + promise getPromise; + string error; + int statusCode = 0; + bool timeoutError = false; + + auto server = std::make_shared(s_port); + server->Callbacks().OnGet = [](const DynamicRequest &) -> ResponseWrapper { + DynamicResponse response; + response.result(http::status::ok); + + // Hold response to test client timeout + promise timer; + timer.get_future().wait_for(std::chrono::milliseconds(4000)); + + return {std::move(response)}; + }; + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + getPromise.set_value(); + }); + resource->SetOnError([&getPromise, &error, &timeoutError](int64_t, string &&errorMessage, bool isTimeout) { + error = std::move(errorMessage); + timeoutError = isTimeout; + getPromise.set_value(); + }); + resource->SendRequest( "GET", std::move(url), 0, /*requestId*/ @@ -534,15 +529,15 @@ namespace Microsoft::React::Test { false, /*withCredentials*/ [](int64_t) {} /*callback*/); - getPromise.get_future().wait(); - server->Stop(); + getPromise.get_future().wait(); + server->Stop(); - Assert::IsTrue(timeoutError); - Assert::AreEqual({ "[0x800705b4] This operation returned because the timeout period expired." }, error); - Assert::AreEqual(0, statusCode); - } - }; + Assert::IsTrue(timeoutError); + Assert::AreEqual({"[0x800705b4] This operation returned because the timeout period expired."}, error); + Assert::AreEqual(0, statusCode); + } +}; - /*static*/ uint16_t HttpResourceIntegrationTest::s_port = 4444; +/*static*/ uint16_t HttpResourceIntegrationTest::s_port = 4444; } // namespace Microsoft::React::Test diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index c3a5be069b8..d6d39abc785 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -25,89 +25,89 @@ using winrt::Windows::Foundation::IInspectable; namespace { - using Microsoft::React::Modules::SendEvent; - using Microsoft::React::Networking::IHttpResource; +using Microsoft::React::Modules::SendEvent; +using Microsoft::React::Networking::IHttpResource; - constexpr char moduleName[] = "Networking"; +constexpr char moduleName[] = "Networking"; - // React event names - constexpr char completedResponse[] = "didCompleteNetworkResponse"; - constexpr char receivedResponse[] = "didReceiveNetworkResponse"; - constexpr char receivedData[] = "didReceiveNetworkData"; - constexpr char receivedDataProgress[] = "didReceiveNetworkDataProgress"; +// React event names +constexpr char completedResponse[] = "didCompleteNetworkResponse"; +constexpr char receivedResponse[] = "didReceiveNetworkResponse"; +constexpr char receivedData[] = "didReceiveNetworkData"; +constexpr char receivedDataProgress[] = "didReceiveNetworkDataProgress"; - static void SetUpHttpResource( +static void SetUpHttpResource( shared_ptr resource, weak_ptr weakReactInstance, - IInspectable& inspectableProperties) { - resource->SetOnRequestSuccess([weakReactInstance](int64_t requestId) { - auto args = dynamic::array(requestId); + IInspectable &inspectableProperties) { + resource->SetOnRequestSuccess([weakReactInstance](int64_t requestId) { + auto args = dynamic::array(requestId); - SendEvent(weakReactInstance, completedResponse, std::move(args)); - }); + SendEvent(weakReactInstance, completedResponse, std::move(args)); + }); - resource->SetOnResponse([weakReactInstance](int64_t requestId, IHttpResource::Response&& response) { - dynamic headers = dynamic::object(); - for (auto& header : response.Headers) { - headers[header.first] = header.second; - } + resource->SetOnResponse([weakReactInstance](int64_t requestId, IHttpResource::Response &&response) { + dynamic headers = dynamic::object(); + for (auto &header : response.Headers) { + headers[header.first] = header.second; + } - // TODO: Test response content. - dynamic args = dynamic::array(requestId, response.StatusCode, headers, response.Url); + // TODO: Test response content. + dynamic args = dynamic::array(requestId, response.StatusCode, headers, response.Url); - SendEvent(weakReactInstance, receivedResponse, std::move(args)); - }); + SendEvent(weakReactInstance, receivedResponse, std::move(args)); + }); - resource->SetOnData([weakReactInstance](int64_t requestId, string&& responseData) { - SendEvent(weakReactInstance, receivedData, dynamic::array(requestId, std::move(responseData))); + resource->SetOnData([weakReactInstance](int64_t requestId, string &&responseData) { + SendEvent(weakReactInstance, receivedData, dynamic::array(requestId, std::move(responseData))); - // TODO: Move into separate method IF not executed right after onData() - SendEvent(weakReactInstance, completedResponse, dynamic::array(requestId)); - }); + // TODO: Move into separate method IF not executed right after onData() + SendEvent(weakReactInstance, completedResponse, dynamic::array(requestId)); + }); - // 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))); - }; - resource->SetOnData(std::move(onDataDynamic)); + // 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))); + }; + resource->SetOnData(std::move(onDataDynamic)); - resource->SetOnError([weakReactInstance](int64_t requestId, string&& message, bool isTimeout) { - dynamic args = dynamic::array(requestId, std::move(message)); - if (isTimeout) { - args.push_back(true); - } + resource->SetOnError([weakReactInstance](int64_t requestId, string &&message, bool isTimeout) { + dynamic args = dynamic::array(requestId, std::move(message)); + if (isTimeout) { + args.push_back(true); + } - SendEvent(weakReactInstance, completedResponse, std::move(args)); - }); - } + SendEvent(weakReactInstance, completedResponse, std::move(args)); + }); +} } // namespace namespace Microsoft::React { - HttpModule::HttpModule(IInspectable const& inspectableProperties) noexcept - : m_holder{ std::make_shared() }, - m_inspectableProperties{ inspectableProperties }, - m_resource{ IHttpResource::Make(inspectableProperties) } { - m_holder->Module = this; - } +HttpModule::HttpModule(IInspectable const &inspectableProperties) noexcept + : m_holder{std::make_shared()}, + m_inspectableProperties{inspectableProperties}, + m_resource{IHttpResource::Make(inspectableProperties)} { + m_holder->Module = this; +} - HttpModule::~HttpModule() noexcept /*override*/ { - m_holder->Module = nullptr; - } +HttpModule::~HttpModule() noexcept /*override*/ { + m_holder->Module = nullptr; +} #pragma region CxxModule - string HttpModule::getName() /*override*/ { - return moduleName; - } +string HttpModule::getName() /*override*/ { + return moduleName; +} - std::map HttpModule::getConstants() { - return {}; - } +std::map HttpModule::getConstants() { + return {}; +} - // clang-format off +// clang-format off std::vector HttpModule::getMethods() { return @@ -192,12 +192,12 @@ namespace Microsoft::React { } }; } - // clang-format on +// clang-format on #pragma endregion CxxModule - /*extern*/ const char* GetHttpModuleName() noexcept { - return moduleName; - } +/*extern*/ const char *GetHttpModuleName() noexcept { + return moduleName; +} } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/WebSocketModule.cpp b/vnext/Shared/Modules/WebSocketModule.cpp index 7a53b4e5789..8ec5e4cbca9 100644 --- a/vnext/Shared/Modules/WebSocketModule.cpp +++ b/vnext/Shared/Modules/WebSocketModule.cpp @@ -37,161 +37,156 @@ using winrt::Windows::Foundation::IInspectable; using winrt::Windows::Security::Cryptography::CryptographicBuffer; namespace { - using Microsoft::React::IWebSocketModuleProxy; - using Microsoft::React::WebSocketModule; - using Microsoft::React::Modules::SendEvent; - using Microsoft::React::Networking::IWebSocketResource; +using Microsoft::React::IWebSocketModuleProxy; +using Microsoft::React::WebSocketModule; +using Microsoft::React::Modules::SendEvent; +using Microsoft::React::Networking::IWebSocketResource; - constexpr char moduleName[] = "WebSocketModule"; +constexpr char moduleName[] = "WebSocketModule"; - static shared_ptr - GetOrCreateWebSocket(int64_t id, string&& url, weak_ptr weakState) { - auto state = weakState.lock(); - if (!state) { +static shared_ptr +GetOrCreateWebSocket(int64_t id, string &&url, weak_ptr weakState) { + auto state = weakState.lock(); + if (!state) { + return nullptr; + } + + auto itr = state->ResourceMap.find(id); + if (itr == state->ResourceMap.end()) { + if (!state->Module) { return nullptr; } + auto weakInstance = state->Module->getInstance(); - auto itr = state->ResourceMap.find(id); - if (itr == state->ResourceMap.end()) { - if (!state->Module) { - return nullptr; - } - auto weakInstance = state->Module->getInstance(); - - shared_ptr ws; - try { - ws = state->ResourceFactory(std::move(url)); - } - catch (const winrt::hresult_error& e) { - std::stringstream ss; - ss << "[" << std::hex << std::showbase << std::setw(8) << static_cast(e.code()) << "] " - << winrt::to_string(e.message()); + shared_ptr ws; + try { + ws = state->ResourceFactory(std::move(url)); + } catch (const winrt::hresult_error &e) { + std::stringstream ss; + ss << "[" << std::hex << std::showbase << std::setw(8) << static_cast(e.code()) << "] " + << winrt::to_string(e.message()); - SendEvent(weakInstance, "webSocketFailed", dynamic::object("id", id)("message", std::move(ss.str()))); + SendEvent(weakInstance, "webSocketFailed", dynamic::object("id", id)("message", std::move(ss.str()))); - return nullptr; - } - catch (const std::exception& e) { - SendEvent(weakInstance, "webSocketFailed", dynamic::object("id", id)("message", e.what())); + return nullptr; + } catch (const std::exception &e) { + SendEvent(weakInstance, "webSocketFailed", dynamic::object("id", id)("message", e.what())); - return nullptr; - } - catch (...) { - SendEvent( + return nullptr; + } catch (...) { + SendEvent( weakInstance, "webSocketFailed", dynamic::object("id", id)("message", "Unidentified error creating IWebSocketResource")); - return nullptr; - } - - ws->SetOnError([id, weakInstance](const IWebSocketResource::Error& err) { - auto strongInstance = weakInstance.lock(); - if (!strongInstance) - return; - - auto errorObj = dynamic::object("id", id)("message", err.Message); - SendEvent(weakInstance, "websocketFailed", std::move(errorObj)); - }); - ws->SetOnConnect([id, weakInstance]() { - auto strongInstance = weakInstance.lock(); - if (!strongInstance) - return; + return nullptr; + } - auto args = dynamic::object("id", id); - SendEvent(weakInstance, "websocketOpen", std::move(args)); - }); - ws->SetOnMessage( - [id, weakInstance, propBag = ReactPropertyBag{ state->InspectableProps.try_as() }]( - size_t length, const string& message, bool isBinary) { - auto strongInstance = weakInstance.lock(); - if (!strongInstance) - return; - - dynamic args = dynamic::object("id", id)("type", isBinary ? "binary" : "text"); - shared_ptr contentHandler; - auto propId = ReactPropertyId>>{ - L"BlobModule.ContentHandler" }; - 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 = std::vector(arr.begin(), arr.end()); - - contentHandler->ProcessMessage(std::move(data), args); - } - else { - contentHandler->ProcessMessage(string{ message }, args); - } - } - else { - args["data"] = message; + ws->SetOnError([id, weakInstance](const IWebSocketResource::Error &err) { + auto strongInstance = weakInstance.lock(); + if (!strongInstance) + return; + + auto errorObj = dynamic::object("id", id)("message", err.Message); + SendEvent(weakInstance, "websocketFailed", std::move(errorObj)); + }); + ws->SetOnConnect([id, weakInstance]() { + auto strongInstance = weakInstance.lock(); + if (!strongInstance) + return; + + auto args = dynamic::object("id", id); + SendEvent(weakInstance, "websocketOpen", std::move(args)); + }); + ws->SetOnMessage( + [id, weakInstance, propBag = ReactPropertyBag{state->InspectableProps.try_as()}]( + size_t length, const string &message, bool isBinary) { + auto strongInstance = weakInstance.lock(); + if (!strongInstance) + return; + + dynamic args = dynamic::object("id", id)("type", isBinary ? "binary" : "text"); + shared_ptr contentHandler; + auto propId = ReactPropertyId>>{ + L"BlobModule.ContentHandler"}; + 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 = std::vector(arr.begin(), arr.end()); + + contentHandler->ProcessMessage(std::move(data), args); + } else { + contentHandler->ProcessMessage(string{message}, args); } + } else { + args["data"] = message; + } - SendEvent(weakInstance, "websocketMessage", std::move(args)); - }); - ws->SetOnClose([id, weakInstance](IWebSocketResource::CloseCode code, const string& reason) { - auto strongInstance = weakInstance.lock(); - if (!strongInstance) - return; - - auto args = dynamic::object("id", id)("code", static_cast(code))("reason", reason); - SendEvent(weakInstance, "websocketClosed", std::move(args)); + SendEvent(weakInstance, "websocketMessage", std::move(args)); }); + ws->SetOnClose([id, weakInstance](IWebSocketResource::CloseCode code, const string &reason) { + auto strongInstance = weakInstance.lock(); + if (!strongInstance) + return; - state->ResourceMap.emplace(id, ws); - return ws; - } + auto args = dynamic::object("id", id)("code", static_cast(code))("reason", reason); + SendEvent(weakInstance, "websocketClosed", std::move(args)); + }); - return itr->second; + state->ResourceMap.emplace(id, ws); + return ws; } + return itr->second; +} + } // anonymous namespace namespace Microsoft::React { #pragma region WebSocketModule - WebSocketModule::WebSocketModule(winrt::Windows::Foundation::IInspectable const& inspectableProperties) - : m_sharedState{ std::make_shared() }, - m_proxy{ std::make_shared(inspectableProperties) } { - m_sharedState->ResourceFactory = [](string&& url) { return IWebSocketResource::Make(); }; - m_sharedState->Module = this; - m_sharedState->InspectableProps = inspectableProperties; +WebSocketModule::WebSocketModule(winrt::Windows::Foundation::IInspectable const &inspectableProperties) + : m_sharedState{std::make_shared()}, + m_proxy{std::make_shared(inspectableProperties)} { + m_sharedState->ResourceFactory = [](string &&url) { return IWebSocketResource::Make(); }; + m_sharedState->Module = this; + m_sharedState->InspectableProps = inspectableProperties; - auto propBag = ReactPropertyBag{ m_sharedState->InspectableProps.try_as() }; + auto propBag = ReactPropertyBag{m_sharedState->InspectableProps.try_as()}; - auto proxyPropId = ReactPropertyId>>{ L"WebSocketModule.Proxy" }; - auto proxy = weak_ptr{ m_proxy }; - propBag.Set(proxyPropId, std::move(proxy)); + auto proxyPropId = ReactPropertyId>>{L"WebSocketModule.Proxy"}; + auto proxy = weak_ptr{m_proxy}; + propBag.Set(proxyPropId, std::move(proxy)); - auto statePropId = ReactPropertyId>>{ L"WebSocketModule.SharedState" }; - auto state = weak_ptr{ m_sharedState }; - propBag.Set(statePropId, std::move(state)); - } + auto statePropId = ReactPropertyId>>{L"WebSocketModule.SharedState"}; + auto state = weak_ptr{m_sharedState}; + propBag.Set(statePropId, std::move(state)); +} - WebSocketModule::~WebSocketModule() noexcept /*override*/ { - m_sharedState->Module = nullptr; - } +WebSocketModule::~WebSocketModule() noexcept /*override*/ { + m_sharedState->Module = nullptr; +} - void WebSocketModule::SetResourceFactory( - std::function(const string&)>&& resourceFactory) { - m_sharedState->ResourceFactory = std::move(resourceFactory); - } +void WebSocketModule::SetResourceFactory( + std::function(const string &)> &&resourceFactory) { + m_sharedState->ResourceFactory = std::move(resourceFactory); +} - string WebSocketModule::getName() { - return moduleName; - } +string WebSocketModule::getName() { + return moduleName; +} - std::map WebSocketModule::getConstants() { - return {}; - } +std::map WebSocketModule::getConstants() { + return {}; +} - // clang-format off +// clang-format off std::vector WebSocketModule::getMethods() { return @@ -294,39 +289,39 @@ namespace Microsoft::React { } }; } // getMethods - // clang-format on +// clang-format on #pragma endregion WebSocketModule #pragma region WebSocketModuleProxy - WebSocketModuleProxy::WebSocketModuleProxy(IInspectable const& inspectableProperties) noexcept - : m_inspectableProps{ inspectableProperties } {} +WebSocketModuleProxy::WebSocketModuleProxy(IInspectable const &inspectableProperties) noexcept + : m_inspectableProps{inspectableProperties} {} - void WebSocketModuleProxy::SendBinary(std::string&& base64String, int64_t id) noexcept /*override*/ { - auto propBag = ReactPropertyBag{ m_inspectableProps.try_as() }; - auto sharedPropId = - ReactPropertyId>>{ L"WebSocketModule.SharedState" }; - auto state = propBag.Get(sharedPropId).Value(); +void WebSocketModuleProxy::SendBinary(std::string &&base64String, int64_t id) noexcept /*override*/ { + auto propBag = ReactPropertyBag{m_inspectableProps.try_as()}; + auto sharedPropId = + ReactPropertyId>>{L"WebSocketModule.SharedState"}; + auto state = propBag.Get(sharedPropId).Value(); - weak_ptr weakWs = GetOrCreateWebSocket(id, {}, std::move(state)); - if (auto sharedWs = weakWs.lock()) { - sharedWs->SendBinary(std::move(base64String)); - } + weak_ptr weakWs = GetOrCreateWebSocket(id, {}, std::move(state)); + if (auto sharedWs = weakWs.lock()) { + sharedWs->SendBinary(std::move(base64String)); } +} #pragma endregion WebSocketModuleProxy - /*extern*/ const char* GetWebSocketModuleName() noexcept { - return moduleName; - } +/*extern*/ const char *GetWebSocketModuleName() noexcept { + return moduleName; +} - /*extern*/ std::unique_ptr CreateWebSocketModule( - IInspectable const& inspectableProperties) noexcept { - if (auto properties = inspectableProperties.try_as()) - return std::make_unique(properties); +/*extern*/ std::unique_ptr CreateWebSocketModule( + IInspectable const &inspectableProperties) noexcept { + if (auto properties = inspectableProperties.try_as()) + return std::make_unique(properties); - return nullptr; - } + return nullptr; +} } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/WebSocketModule.h b/vnext/Shared/Modules/WebSocketModule.h index b637cc182bd..5a10ac5990a 100644 --- a/vnext/Shared/Modules/WebSocketModule.h +++ b/vnext/Shared/Modules/WebSocketModule.h @@ -14,92 +14,92 @@ namespace Microsoft::React { - class WebSocketModuleProxy final : public IWebSocketModuleProxy { - // Property bag high level reference. - winrt::Windows::Foundation::IInspectable m_inspectableProps; +class WebSocketModuleProxy final : public IWebSocketModuleProxy { + // Property bag high level reference. + winrt::Windows::Foundation::IInspectable m_inspectableProps; - public: - WebSocketModuleProxy(winrt::Windows::Foundation::IInspectable const& inspectableProperties) noexcept; + public: + WebSocketModuleProxy(winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept; #pragma region IWebSocketModuleProxy - void SendBinary(std::string&& base64String, int64_t id) noexcept override; + void SendBinary(std::string &&base64String, int64_t id) noexcept override; #pragma endregion - }; +}; + +/// +/// Realizes NativeModules projection. +/// See react-native/Libraries/WebSocket/WebSocket.js +/// +class WebSocketModule : public facebook::xplat::module::CxxModule { + public: + enum MethodId { Connect = 0, Close = 1, Send = 2, SendBinary = 3, Ping = 4, SIZE = 5 }; + + WebSocketModule(winrt::Windows::Foundation::IInspectable const &inspectableProperties); + + ~WebSocketModule() noexcept override; - /// - /// Realizes NativeModules projection. - /// See react-native/Libraries/WebSocket/WebSocket.js - /// - class WebSocketModule : public facebook::xplat::module::CxxModule{ - public: - enum MethodId { Connect = 0, Close = 1, Send = 2, SendBinary = 3, Ping = 4, SIZE = 5 }; - - WebSocketModule(winrt::Windows::Foundation::IInspectable const& inspectableProperties); - - ~WebSocketModule() noexcept override; - - struct SharedState { - /// - /// Keeps IWebSocketResource instances identified by id. - /// As defined in WebSocket.js. - /// - std::map> ResourceMap{}; - - /// - /// Generates IWebSocketResource instances, defaulting to IWebSocketResource::Make. - /// - std::function(std::string&&)> ResourceFactory; - - /// - /// Keeps a raw reference to the module object to lazily retrieve the React Instance as needed. - /// - CxxModule* Module{nullptr}; - - // Property bag high level reference. - winrt::Windows::Foundation::IInspectable InspectableProps; - }; - - #pragma region CxxModule overrides - - /// - /// - /// - std::string getName() override; - - /// - /// - /// - std::map getConstants() override; - - /// - /// - /// - /// See See react-native/Libraries/WebSocket/WebSocket.js - std::vector getMethods() override; - - #pragma endregion CxxModule overrides - - void SetResourceFactory( - std::function(const std::string&)>&& resourceFactory); - - private: - /// - /// Keeps IWebSocketResource instances identified by id. - /// As defined in WebSocket.js. - /// - std::map> m_webSockets; - - /// - /// Keeps members that can be accessed threads other than this module's owner accessible. - /// - std::shared_ptr m_sharedState; - - /// - /// Exposes a subset of the module's methods. - /// - std::shared_ptr m_proxy; + struct SharedState { + /// + /// Keeps IWebSocketResource instances identified by id. + /// As defined in WebSocket.js. + /// + std::map> ResourceMap{}; + + /// + /// Generates IWebSocketResource instances, defaulting to IWebSocketResource::Make. + /// + std::function(std::string &&)> ResourceFactory; + + /// + /// Keeps a raw reference to the module object to lazily retrieve the React Instance as needed. + /// + CxxModule *Module{nullptr}; + + // Property bag high level reference. + winrt::Windows::Foundation::IInspectable InspectableProps; }; +#pragma region CxxModule overrides + + /// + /// + /// + std::string getName() override; + + /// + /// + /// + std::map getConstants() override; + + /// + /// + /// + /// See See react-native/Libraries/WebSocket/WebSocket.js + std::vector getMethods() override; + +#pragma endregion CxxModule overrides + + void SetResourceFactory( + std::function(const std::string &)> &&resourceFactory); + + private: + /// + /// Keeps IWebSocketResource instances identified by id. + /// As defined in WebSocket.js. + /// + std::map> m_webSockets; + + /// + /// Keeps members that can be accessed threads other than this module's owner accessible. + /// + std::shared_ptr m_sharedState; + + /// + /// Exposes a subset of the module's methods. + /// + std::shared_ptr m_proxy; +}; + } // namespace Microsoft::React diff --git a/vnext/Shared/Networking/RedirectHttpFilter.cpp b/vnext/Shared/Networking/RedirectHttpFilter.cpp index a5248e031f8..0ed67360b29 100644 --- a/vnext/Shared/Networking/RedirectHttpFilter.cpp +++ b/vnext/Shared/Networking/RedirectHttpFilter.cpp @@ -212,8 +212,7 @@ ResponseOperation RedirectHttpFilter::SendRequestAsync(HttpRequestMessage const do { // Send subsequent requests through the filter that doesn't have the credentials included in the first request - response = - co_await (redirectCount > 0 ? m_innerFilterWithNoCredentials : m_innerFilter).SendRequestAsync(coRequest); + response = co_await(redirectCount > 0 ? m_innerFilterWithNoCredentials : m_innerFilter).SendRequestAsync(coRequest); // Stop redirecting when a non-redirect status is responded. if (response.StatusCode() != HttpStatusCode::MultipleChoices && diff --git a/vnext/Shared/Networking/WinRTHttpResource.h b/vnext/Shared/Networking/WinRTHttpResource.h index ef9c4a854ed..23cc68dd299 100644 --- a/vnext/Shared/Networking/WinRTHttpResource.h +++ b/vnext/Shared/Networking/WinRTHttpResource.h @@ -17,83 +17,83 @@ namespace Microsoft::React::Networking { - class WinRTHttpResource : public IHttpResource, - public IHttpModuleProxy, - public IWinRTHttpRequestFactory, - public std::enable_shared_from_this { - winrt::Windows::Web::Http::IHttpClient m_client; - std::mutex m_mutex; - std::unordered_map m_responses; +class WinRTHttpResource : public IHttpResource, + public IHttpModuleProxy, + public IWinRTHttpRequestFactory, + public std::enable_shared_from_this { + winrt::Windows::Web::Http::IHttpClient m_client; + std::mutex m_mutex; + std::unordered_map m_responses; - std::function m_onRequestSuccess; - std::function m_onResponse; - std::function m_onData; - std::function m_onDataDynamic; - std::function m_onError; + std::function m_onRequestSuccess; + std::function m_onResponse; + std::function m_onData; + std::function m_onDataDynamic; + std::function m_onError; - // Used for IHttpModuleProxy - std::weak_ptr m_uriHandler; - std::weak_ptr m_requestBodyHandler; - std::weak_ptr m_responseHandler; + // Used for IHttpModuleProxy + std::weak_ptr m_uriHandler; + std::weak_ptr m_requestBodyHandler; + std::weak_ptr m_responseHandler; - void TrackResponse(int64_t requestId, ResponseOperation response) noexcept; + void TrackResponse(int64_t requestId, ResponseOperation response) noexcept; - void UntrackResponse(int64_t requestId) noexcept; + void UntrackResponse(int64_t requestId) noexcept; - winrt::fire_and_forget PerformSendRequest( - winrt::Windows::Web::Http::HttpMethod&& method, - winrt::Windows::Foundation::Uri&& uri, - winrt::Windows::Foundation::IInspectable const& args) noexcept; + winrt::fire_and_forget PerformSendRequest( + winrt::Windows::Web::Http::HttpMethod &&method, + winrt::Windows::Foundation::Uri &&uri, + winrt::Windows::Foundation::IInspectable const &args) noexcept; - public: - WinRTHttpResource() noexcept; + public: + WinRTHttpResource() noexcept; - WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient&& client) noexcept; + WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient &&client) noexcept; #pragma region IWinRTHttpRequestFactory - winrt::Windows::Foundation::IAsyncOperation CreateRequest( - winrt::Windows::Web::Http::HttpMethod&& method, - winrt::Windows::Foundation::Uri&& uri, + winrt::Windows::Foundation::IAsyncOperation CreateRequest( + winrt::Windows::Web::Http::HttpMethod &&method, + winrt::Windows::Foundation::Uri &&uri, winrt::Windows::Foundation::Collections::IMap - props) noexcept override; + props) noexcept override; #pragma endregion IWinRTHttpRequestFactory #pragma region IHttpResource - void SendRequest( - std::string&& method, - std::string&& url, + void SendRequest( + std::string &&method, + std::string &&url, int64_t requestId, - Headers&& headers, - folly::dynamic&& data, - std::string&& responseType, + Headers &&headers, + folly::dynamic &&data, + std::string &&responseType, bool useIncrementalUpdates, int64_t timeout, bool withCredentials, - std::function&& callback) noexcept override; - void AbortRequest(int64_t requestId) noexcept override; - void ClearCookies() noexcept override; + std::function &&callback) noexcept override; + void AbortRequest(int64_t requestId) noexcept override; + void ClearCookies() noexcept override; - 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 SetOnError( - std::function&& handler) noexcept override; + 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 SetOnError( + std::function &&handler) noexcept override; #pragma endregion IHttpResource #pragma region IHttpModuleProxy - void AddUriHandler(std::shared_ptr uriHandler) noexcept override; + void AddUriHandler(std::shared_ptr uriHandler) noexcept override; - void AddRequestBodyHandler(std::shared_ptr requestBodyHandler) noexcept override; + void AddRequestBodyHandler(std::shared_ptr requestBodyHandler) noexcept override; - void AddResponseHandler(std::shared_ptr responseHandler) noexcept override; + void AddResponseHandler(std::shared_ptr responseHandler) noexcept override; #pragma endregion IHttpModuleProxy - }; +}; } // namespace Microsoft::React::Networking diff --git a/vnext/Shared/Networking/WinRTTypes.h b/vnext/Shared/Networking/WinRTTypes.h index 1c4d57910fa..d0bbafc27a0 100644 --- a/vnext/Shared/Networking/WinRTTypes.h +++ b/vnext/Shared/Networking/WinRTTypes.h @@ -16,18 +16,18 @@ namespace Microsoft::React::Networking { - struct RequestArgs : public winrt::implements { - int64_t RequestId; - IHttpResource::Headers Headers; - folly::dynamic Data; - bool IncrementalUpdates; - bool WithCredentials; - std::string ResponseType; - int64_t Timeout; - }; - - typedef winrt::Windows::Foundation:: +struct RequestArgs : public winrt::implements { + int64_t RequestId; + IHttpResource::Headers Headers; + folly::dynamic Data; + bool IncrementalUpdates; + bool WithCredentials; + std::string ResponseType; + int64_t Timeout; +}; + +typedef winrt::Windows::Foundation:: IAsyncOperationWithProgress - ResponseOperation; + ResponseOperation; } // namespace Microsoft::React::Networking diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index aae9034a796..6054526c272 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -71,187 +71,185 @@ using winrt::Microsoft::ReactNative::ReactPropertyBagHelper; namespace Microsoft::React { - /*extern*/ std::unique_ptr CreateHttpModule( - winrt::Windows::Foundation::IInspectable const& inspectableProperties) noexcept { - if (GetRuntimeOptionBool("Http.UseMonolithicModule")) { - return std::make_unique(); - } - else { - return std::make_unique(inspectableProperties); - } +/*extern*/ std::unique_ptr CreateHttpModule( + winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept { + if (GetRuntimeOptionBool("Http.UseMonolithicModule")) { + return std::make_unique(); + } else { + return std::make_unique(inspectableProperties); } +} } // namespace Microsoft::React namespace facebook { - namespace react { - - namespace { - - class OJSIExecutorFactory : public JSExecutorFactory { - public: - std::unique_ptr createJSExecutor( - std::shared_ptr delegate, - std::shared_ptr jsQueue) override { - Logger logger; - if (loggingHook_) { - // TODO :: Ensure the logLevels are mapped properly. - logger = [loggingHook = std::move(loggingHook_)](const std::string& message, unsigned int logLevel) { - loggingHook(static_cast(logLevel), message.c_str()); - }; - } - else { - logger = [loggingHook = std::move(loggingHook_)](const std::string& /*message*/, unsigned int /*logLevel*/) {}; - } - bindNativeLogger(*runtimeHolder_->getRuntime(), logger); - - auto turboModuleManager = std::make_shared(turboModuleRegistry_, jsCallInvoker_); - - // TODO: The binding here should also add the proxys that convert cxxmodules into turbomodules - // [vmoroz] Note, that we must not use the RN TurboCxxModule.h code because it uses global LongLivedObjectCollection - // instance that prevents us from using multiple RN instance in the same process. - auto binding = [turboModuleManager](const std::string& name) -> std::shared_ptr { - return turboModuleManager->getModule(name); - }; - - TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding), longLivedObjectCollection_); - - // init TurboModule - for (const auto& moduleName : turboModuleManager->getEagerInitModuleNames()) { - turboModuleManager->getModule(moduleName); - } - - return std::make_unique( - runtimeHolder_->getRuntime(), - std::move(delegate), - JSIExecutor::defaultTimeoutInvoker, - [isProfiling = isProfilingEnabled_]([[maybe_unused]] jsi::Runtime& runtime) { -#ifdef ENABLE_JS_SYSTRACE_TO_ETW - facebook::react::tracing::initializeJSHooks(runtime, isProfiling); -#endif - }); - } - - OJSIExecutorFactory( - std::shared_ptr runtimeHolder, - NativeLoggingHook loggingHook, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - bool isProfilingEnabled, - std::shared_ptr jsCallInvoker) noexcept - : runtimeHolder_{ std::move(runtimeHolder) }, - loggingHook_{ std::move(loggingHook) }, - turboModuleRegistry_{ std::move(turboModuleRegistry) }, - longLivedObjectCollection_{ std::move(longLivedObjectCollection) }, - jsCallInvoker_{ std::move(jsCallInvoker) }, - isProfilingEnabled_{ isProfilingEnabled } {} - - private: - std::shared_ptr runtimeHolder_; - std::shared_ptr turboModuleRegistry_; - std::shared_ptr longLivedObjectCollection_; - std::shared_ptr jsCallInvoker_; - NativeLoggingHook loggingHook_; - bool isProfilingEnabled_; +namespace react { + +namespace { + +class OJSIExecutorFactory : public JSExecutorFactory { + public: + std::unique_ptr createJSExecutor( + std::shared_ptr delegate, + std::shared_ptr jsQueue) override { + Logger logger; + if (loggingHook_) { + // TODO :: Ensure the logLevels are mapped properly. + logger = [loggingHook = std::move(loggingHook_)](const std::string &message, unsigned int logLevel) { + loggingHook(static_cast(logLevel), message.c_str()); }; + } else { + logger = [loggingHook = std::move(loggingHook_)](const std::string & /*message*/, unsigned int /*logLevel*/) {}; + } + bindNativeLogger(*runtimeHolder_->getRuntime(), logger); - } // namespace + auto turboModuleManager = std::make_shared(turboModuleRegistry_, jsCallInvoker_); - void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const char* /*tag*/) {} + // TODO: The binding here should also add the proxys that convert cxxmodules into turbomodules + // [vmoroz] Note, that we must not use the RN TurboCxxModule.h code because it uses global LongLivedObjectCollection + // instance that prevents us from using multiple RN instance in the same process. + auto binding = [turboModuleManager](const std::string &name) -> std::shared_ptr { + return turboModuleManager->getModule(name); + }; - /*static*/ std::shared_ptr InstanceImpl::MakeNoBundle( - std::shared_ptr&& instance, - std::string&& jsBundleBasePath, - std::vector< - std::tuple>> - && cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr&& callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept { - auto inner = std::shared_ptr(new InstanceImpl( - std::move(instance), - std::move(jsBundleBasePath), - std::move(cxxModules), - std::move(turboModuleRegistry), - std::move(longLivedObjectCollection), - std::move(callback), - std::move(jsQueue), - std::move(nativeQueue), - std::move(devSettings), - std::move(devManager))); - - inner->RegisterForReloadIfNecessary(); - - return inner; - } + TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding), longLivedObjectCollection_); - /*static*/ std::shared_ptr InstanceImpl::MakeAndLoadBundle( - std::shared_ptr&& instance, - std::string&& jsBundleBasePath, - std::string&& jsBundleRelativePath, - std::vector< - std::tuple>> - && cxxModules, - std::shared_ptr turboModuleRegistry, - std::unique_ptr&& callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept { - auto inner = std::shared_ptr(new InstanceImpl( - std::move(instance), - std::move(jsBundleBasePath), - std::move(cxxModules), - std::move(turboModuleRegistry), - nullptr, - std::move(callback), - std::move(jsQueue), - std::move(nativeQueue), - std::move(devSettings), - std::move(devManager))); - - inner->loadBundle(std::move(jsBundleRelativePath)); - inner->RegisterForReloadIfNecessary(); - - return inner; + // init TurboModule + for (const auto &moduleName : turboModuleManager->getEagerInitModuleNames()) { + turboModuleManager->getModule(moduleName); } - void InstanceImpl::SetInError() noexcept { - m_isInError = true; - } + return std::make_unique( + runtimeHolder_->getRuntime(), + std::move(delegate), + JSIExecutor::defaultTimeoutInvoker, + [isProfiling = isProfilingEnabled_]([[maybe_unused]] jsi::Runtime &runtime) { +#ifdef ENABLE_JS_SYSTRACE_TO_ETW + facebook::react::tracing::initializeJSHooks(runtime, isProfiling); +#endif + }); + } - namespace { - bool shouldStartHermesInspector(DevSettings& devSettings) { - bool isHermes = - ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || - (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && - devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); - - if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) - return true; - else - return false; - } - } // namespace - - InstanceImpl::InstanceImpl( - std::shared_ptr&& instance, - std::string&& jsBundleBasePath, - std::vector< - std::tuple>> - && cxxModules, + OJSIExecutorFactory( + std::shared_ptr runtimeHolder, + NativeLoggingHook loggingHook, std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr&& callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) - : m_turboModuleRegistry(std::move(turboModuleRegistry)), + std::shared_ptr longLivedObjectCollection, + bool isProfilingEnabled, + std::shared_ptr jsCallInvoker) noexcept + : runtimeHolder_{std::move(runtimeHolder)}, + loggingHook_{std::move(loggingHook)}, + turboModuleRegistry_{std::move(turboModuleRegistry)}, + longLivedObjectCollection_{std::move(longLivedObjectCollection)}, + jsCallInvoker_{std::move(jsCallInvoker)}, + isProfilingEnabled_{isProfilingEnabled} {} + + private: + std::shared_ptr runtimeHolder_; + std::shared_ptr turboModuleRegistry_; + std::shared_ptr longLivedObjectCollection_; + std::shared_ptr jsCallInvoker_; + NativeLoggingHook loggingHook_; + bool isProfilingEnabled_; +}; + +} // namespace + +void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const char * /*tag*/) {} + +/*static*/ std::shared_ptr InstanceImpl::MakeNoBundle( + std::shared_ptr &&instance, + std::string &&jsBundleBasePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept { + auto inner = std::shared_ptr(new InstanceImpl( + std::move(instance), + std::move(jsBundleBasePath), + std::move(cxxModules), + std::move(turboModuleRegistry), + std::move(longLivedObjectCollection), + std::move(callback), + std::move(jsQueue), + std::move(nativeQueue), + std::move(devSettings), + std::move(devManager))); + + inner->RegisterForReloadIfNecessary(); + + return inner; +} + +/*static*/ std::shared_ptr InstanceImpl::MakeAndLoadBundle( + std::shared_ptr &&instance, + std::string &&jsBundleBasePath, + std::string &&jsBundleRelativePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept { + auto inner = std::shared_ptr(new InstanceImpl( + std::move(instance), + std::move(jsBundleBasePath), + std::move(cxxModules), + std::move(turboModuleRegistry), + nullptr, + std::move(callback), + std::move(jsQueue), + std::move(nativeQueue), + std::move(devSettings), + std::move(devManager))); + + inner->loadBundle(std::move(jsBundleRelativePath)); + inner->RegisterForReloadIfNecessary(); + + return inner; +} + +void InstanceImpl::SetInError() noexcept { + m_isInError = true; +} + +namespace { +bool shouldStartHermesInspector(DevSettings &devSettings) { + bool isHermes = + ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || + (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && + devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); + + if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) + return true; + else + return false; +} +} // namespace + +InstanceImpl::InstanceImpl( + std::shared_ptr &&instance, + std::string &&jsBundleBasePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) + : m_turboModuleRegistry(std::move(turboModuleRegistry)), m_longLivedObjectCollection(std::move(longLivedObjectCollection)), m_jsThread(std::move(jsQueue)), m_nativeQueue(nativeQueue), @@ -259,428 +257,419 @@ namespace facebook { m_devSettings(std::move(devSettings)), m_devManager(std::move(devManager)), m_innerInstance(std::move(instance)) { - // Temp set the logmarker here - facebook::react::ReactMarker::logTaggedMarker = logMarker; + // Temp set the logmarker here + facebook::react::ReactMarker::logTaggedMarker = logMarker; #ifdef ENABLE_ETW_TRACING - // TODO :: Find a better place to initialize ETW once per process. - facebook::react::tracing::initializeETW(); + // TODO :: Find a better place to initialize ETW once per process. + facebook::react::tracing::initializeETW(); #endif - if (shouldStartHermesInspector(*m_devSettings)) { - m_devManager->StartInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort); - } + if (shouldStartHermesInspector(*m_devSettings)) { + m_devManager->StartInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort); + } - // Default (common) NativeModules - auto modules = GetDefaultNativeModules(nativeQueue); + // Default (common) NativeModules + auto modules = GetDefaultNativeModules(nativeQueue); - // Add app provided modules. - for (auto& cxxModule : cxxModules) { - modules.push_back(std::make_unique( - m_innerInstance, move(std::get<0>(cxxModule)), move(std::get<1>(cxxModule)), move(std::get<2>(cxxModule)))); - } - m_moduleRegistry = std::make_shared(std::move(modules)); - - // Choose JSExecutor - std::shared_ptr jsef; - if (m_devSettings->useWebDebugger) { - try { - auto jseFunc = m_devManager->LoadJavaScriptInProxyMode(*m_devSettings, [weakthis = weak_from_this()]() { - if (auto strongThis = weakthis.lock()) { - strongThis->SetInError(); - } - }); - - if ((jseFunc == nullptr) || m_isInError) { - m_devSettings->errorCallback("Failed to create JavaScript Executor."); - return; - } - - jsef = std::make_shared(std::move(jseFunc)); - } - catch (std::exception& e) { - m_devSettings->errorCallback(e.what()); - return; + // Add app provided modules. + for (auto &cxxModule : cxxModules) { + modules.push_back(std::make_unique( + m_innerInstance, move(std::get<0>(cxxModule)), move(std::get<1>(cxxModule)), move(std::get<2>(cxxModule)))); + } + m_moduleRegistry = std::make_shared(std::move(modules)); + + // Choose JSExecutor + std::shared_ptr jsef; + if (m_devSettings->useWebDebugger) { + try { + auto jseFunc = m_devManager->LoadJavaScriptInProxyMode(*m_devSettings, [weakthis = weak_from_this()]() { + if (auto strongThis = weakthis.lock()) { + strongThis->SetInError(); } + }); + + if ((jseFunc == nullptr) || m_isInError) { + m_devSettings->errorCallback("Failed to create JavaScript Executor."); + return; } - else { - if (m_devSettings->useFastRefresh || m_devSettings->liveReloadCallback) { - Microsoft::ReactNative::PackagerConnection::CreateOrReusePackagerConnection(*m_devSettings); - } - // If the consumer gives us a JSI runtime, then use it. - if (m_devSettings->jsiRuntimeHolder) { - assert(m_devSettings->jsiEngineOverride == JSIEngineOverride::Default); - jsef = std::make_shared( - m_devSettings->jsiRuntimeHolder, - m_devSettings->loggingCallback, - m_turboModuleRegistry, - m_longLivedObjectCollection, - !m_devSettings->useFastRefresh, - m_innerInstance->getJSCallInvoker()); - } - else { - assert(m_devSettings->jsiEngineOverride != JSIEngineOverride::Default); - switch (m_devSettings->jsiEngineOverride) { - case JSIEngineOverride::Hermes: - m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread); - break; - case JSIEngineOverride::V8: { + jsef = std::make_shared(std::move(jseFunc)); + } catch (std::exception &e) { + m_devSettings->errorCallback(e.what()); + return; + } + } else { + if (m_devSettings->useFastRefresh || m_devSettings->liveReloadCallback) { + Microsoft::ReactNative::PackagerConnection::CreateOrReusePackagerConnection(*m_devSettings); + } + + // If the consumer gives us a JSI runtime, then use it. + if (m_devSettings->jsiRuntimeHolder) { + assert(m_devSettings->jsiEngineOverride == JSIEngineOverride::Default); + jsef = std::make_shared( + m_devSettings->jsiRuntimeHolder, + m_devSettings->loggingCallback, + m_turboModuleRegistry, + m_longLivedObjectCollection, + !m_devSettings->useFastRefresh, + m_innerInstance->getJSCallInvoker()); + } else { + assert(m_devSettings->jsiEngineOverride != JSIEngineOverride::Default); + switch (m_devSettings->jsiEngineOverride) { + case JSIEngineOverride::Hermes: + m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread); + break; + case JSIEngineOverride::V8: { #if defined(USE_V8) - std::unique_ptr scriptStore = nullptr; - std::unique_ptr preparedScriptStore = nullptr; + std::unique_ptr scriptStore = nullptr; + std::unique_ptr preparedScriptStore = nullptr; - char tempPath[MAX_PATH]; - if (GetTempPathA(MAX_PATH, tempPath)) { - preparedScriptStore = std::make_unique(tempPath); - } + char tempPath[MAX_PATH]; + if (GetTempPathA(MAX_PATH, tempPath)) { + preparedScriptStore = std::make_unique(tempPath); + } - m_devSettings->jsiRuntimeHolder = std::make_shared( + m_devSettings->jsiRuntimeHolder = std::make_shared( m_devSettings, m_jsThread, std::move(scriptStore), std::move(preparedScriptStore)); - break; + break; #else - assert(false); // V8 is not available in this build, fallthrough - [[fallthrough]]; + assert(false); // V8 is not available in this build, fallthrough + [[fallthrough]]; #endif - } - case JSIEngineOverride::V8NodeApi: { + } + case JSIEngineOverride::V8NodeApi: { #if defined(USE_V8) - std::unique_ptr preparedScriptStore; + std::unique_ptr preparedScriptStore; - wchar_t tempPath[MAX_PATH]; - if (GetTempPathW(static_cast(std::size(tempPath)), tempPath)) { - preparedScriptStore = + wchar_t tempPath[MAX_PATH]; + if (GetTempPathW(static_cast(std::size(tempPath)), tempPath)) { + preparedScriptStore = std::make_unique(winrt::to_string(tempPath)); - } + } - if (!preparedScriptStore) { - if (m_devSettings->errorCallback) - m_devSettings->errorCallback("Could not initialize prepared script store"); + if (!preparedScriptStore) { + if (m_devSettings->errorCallback) + m_devSettings->errorCallback("Could not initialize prepared script store"); - break; - } + break; + } - m_devSettings->jsiRuntimeHolder = make_shared( + m_devSettings->jsiRuntimeHolder = make_shared( m_devSettings, m_jsThread, nullptr /*scriptStore*/, std::move(preparedScriptStore)); - break; + break; #else - if (m_devSettings->errorCallback) - m_devSettings->errorCallback("JSI/V8/NAPI engine is not available in this build"); - assert(false); - [[fallthrough]]; + if (m_devSettings->errorCallback) + m_devSettings->errorCallback("JSI/V8/NAPI engine is not available in this build"); + assert(false); + [[fallthrough]]; #endif - } - case JSIEngineOverride::Chakra: - case JSIEngineOverride::ChakraCore: - default: // TODO: Add other engines once supported - m_devSettings->jsiRuntimeHolder = - std::make_shared(m_devSettings, m_jsThread, nullptr, nullptr); - break; - } - jsef = std::make_shared( - m_devSettings->jsiRuntimeHolder, - m_devSettings->loggingCallback, - m_turboModuleRegistry, - m_longLivedObjectCollection, - !m_devSettings->useFastRefresh, - m_innerInstance->getJSCallInvoker()); } + case JSIEngineOverride::Chakra: + case JSIEngineOverride::ChakraCore: + default: // TODO: Add other engines once supported + m_devSettings->jsiRuntimeHolder = + std::make_shared(m_devSettings, m_jsThread, nullptr, nullptr); + break; } - - m_innerInstance->initializeBridge(std::move(callback), jsef, m_jsThread, m_moduleRegistry); - - // All JSI runtimes do support host objects and hence the native modules - // proxy. - const bool isNativeModulesProxyAvailable = ((m_devSettings->jsiRuntimeHolder != nullptr) || - (m_devSettings->jsiEngineOverride != JSIEngineOverride::Default)) && - !m_devSettings->useWebDebugger; - if (!isNativeModulesProxyAvailable) { - folly::dynamic configArray = folly::dynamic::array; - for (auto const& moduleName : m_moduleRegistry->moduleNames()) { - auto moduleConfig = m_moduleRegistry->getConfig(moduleName); - configArray.push_back(moduleConfig ? std::move(moduleConfig->config) : nullptr); - } - - folly::dynamic configs = folly::dynamic::object("remoteModuleConfig", std::move(configArray)); - m_innerInstance->setGlobalVariable( - "__fbBatchedBridgeConfig", std::make_unique(folly::toJson(configs))); - } - } - - void InstanceImpl::loadBundle(std::string&& jsBundleRelativePath) { - loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ false); + jsef = std::make_shared( + m_devSettings->jsiRuntimeHolder, + m_devSettings->loggingCallback, + m_turboModuleRegistry, + m_longLivedObjectCollection, + !m_devSettings->useFastRefresh, + m_innerInstance->getJSCallInvoker()); } + } - void InstanceImpl::loadBundleSync(std::string&& jsBundleRelativePath) { - loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ true); + m_innerInstance->initializeBridge(std::move(callback), jsef, m_jsThread, m_moduleRegistry); + + // All JSI runtimes do support host objects and hence the native modules + // proxy. + const bool isNativeModulesProxyAvailable = ((m_devSettings->jsiRuntimeHolder != nullptr) || + (m_devSettings->jsiEngineOverride != JSIEngineOverride::Default)) && + !m_devSettings->useWebDebugger; + if (!isNativeModulesProxyAvailable) { + folly::dynamic configArray = folly::dynamic::array; + for (auto const &moduleName : m_moduleRegistry->moduleNames()) { + auto moduleConfig = m_moduleRegistry->getConfig(moduleName); + configArray.push_back(moduleConfig ? std::move(moduleConfig->config) : nullptr); } - // Note: Based on - // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactCommon/cxxreact/Instance.cpp#L112 - bool isHBCBundle(const std::string& bundle) { - static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3; - - // Note:: Directly access the pointer to avoid copy/length-check. It matters as this string contains the bundle which - // can be potentially huge. - // https://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/#comment-483 - auto header = reinterpret_cast(&bundle[0]); - if (HBCBundleMagicNumber == folly::Endian::little(header->magic)) { - return true; - } - else { - return false; - } - } + folly::dynamic configs = folly::dynamic::object("remoteModuleConfig", std::move(configArray)); + m_innerInstance->setGlobalVariable( + "__fbBatchedBridgeConfig", std::make_unique(folly::toJson(configs))); + } +} + +void InstanceImpl::loadBundle(std::string &&jsBundleRelativePath) { + loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ false); +} + +void InstanceImpl::loadBundleSync(std::string &&jsBundleRelativePath) { + loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ true); +} + +// Note: Based on +// https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactCommon/cxxreact/Instance.cpp#L112 +bool isHBCBundle(const std::string &bundle) { + static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3; + + // Note:: Directly access the pointer to avoid copy/length-check. It matters as this string contains the bundle which + // can be potentially huge. + // https://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/#comment-483 + auto header = reinterpret_cast(&bundle[0]); + if (HBCBundleMagicNumber == folly::Endian::little(header->magic)) { + return true; + } else { + return false; + } +} - void InstanceImpl::loadBundleInternal(std::string&& jsBundleRelativePath, bool synchronously) { - try { - if (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback != nullptr || - m_devSettings->useFastRefresh) { - // First attempt to get download the Js locally, to catch any bundling - // errors before attempting to load the actual script. +void InstanceImpl::loadBundleInternal(std::string &&jsBundleRelativePath, bool synchronously) { + try { + if (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback != nullptr || + m_devSettings->useFastRefresh) { + // First attempt to get download the Js locally, to catch any bundling + // errors before attempting to load the actual script. - uint32_t hermesBytecodeVersion = 0; + uint32_t hermesBytecodeVersion = 0; #if defined(USE_HERMES) && defined(ENABLE_DEVSERVER_HBCBUNDLES) - hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; + hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; #endif - auto [jsBundleString, success] = Microsoft::ReactNative::GetJavaScriptFromServer( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, - m_devSettings->platformName, - true /* dev */, - m_devSettings->useFastRefresh, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion); + auto [jsBundleString, success] = Microsoft::ReactNative::GetJavaScriptFromServer( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, + m_devSettings->platformName, + true /* dev */, + m_devSettings->useFastRefresh, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion); - if (!success) { - m_devManager->UpdateBundleStatus(false, -1); - m_devSettings->errorCallback(jsBundleString); - return; - } + if (!success) { + m_devManager->UpdateBundleStatus(false, -1); + m_devSettings->errorCallback(jsBundleString); + return; + } - int64_t currentTimeInMilliSeconds = - std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) - .count(); - m_devManager->UpdateBundleStatus(true, currentTimeInMilliSeconds); + int64_t currentTimeInMilliSeconds = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); + m_devManager->UpdateBundleStatus(true, currentTimeInMilliSeconds); - auto bundleUrl = DevServerHelper::get_BundleUrl( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, - m_devSettings->platformName, - /*dev*/ true, - /*hot*/ false, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion); - - // This code is based on the HBC Bundle integration on Android - // Ref: - // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp#L231 - if (isHBCBundle(jsBundleString)) { - auto script = std::make_unique(jsBundleString, false); - const char* buffer = script->c_str(); - uint32_t bufferLength = (uint32_t)script->size(); - - // Please refer the code here for details on the file format: - // https://github.com/facebook/metro/blob/b1bacf52070be62872d6bd3420f37a4405ed34e6/packages/metro/src/lib/bundleToBytecode.js#L29 - // Essentially, there is an 8 byte long file header with 4 bytes of a magic number followed by 4 bytes to encode - // the number of modules.The module buffers follows, each one starts with 4 byte header which encodes module - // length.A properly formatted HBCB should have at least 8 bytes.. - uint32_t offset = 8; + auto bundleUrl = DevServerHelper::get_BundleUrl( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, + m_devSettings->platformName, + /*dev*/ true, + /*hot*/ false, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion); + + // This code is based on the HBC Bundle integration on Android + // Ref: + // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp#L231 + if (isHBCBundle(jsBundleString)) { + auto script = std::make_unique(jsBundleString, false); + const char *buffer = script->c_str(); + uint32_t bufferLength = (uint32_t)script->size(); + + // Please refer the code here for details on the file format: + // https://github.com/facebook/metro/blob/b1bacf52070be62872d6bd3420f37a4405ed34e6/packages/metro/src/lib/bundleToBytecode.js#L29 + // Essentially, there is an 8 byte long file header with 4 bytes of a magic number followed by 4 bytes to encode + // the number of modules.The module buffers follows, each one starts with 4 byte header which encodes module + // length.A properly formatted HBCB should have at least 8 bytes.. + uint32_t offset = 8; #define __SAFEADD__(s1, s2, t) \ if (!msl::utilities::SafeAdd(s1, s2, t)) \ break; - while (offset < bufferLength) { - uint32_t segment; - __SAFEADD__(offset, 4, segment) - uint32_t moduleLength = (bufferLength < segment) ? 0 : *(((uint32_t*)buffer) + offset / 4); - - // Early break if the module length is computed as 0.. as the segment start may be overflowing the buffer. - if (moduleLength == 0) - break; - - uint32_t segmentEnd; - __SAFEADD__(moduleLength, segment, segmentEnd) - // Early break if the segment overflows beyond the buffer. This is unlikely for a properly formatted - // HBCB though. - if (segmentEnd > bufferLength) - break; - - m_innerInstance->loadScriptFromString( - std::make_unique(std::string(buffer + segment, buffer + segmentEnd)), - bundleUrl, - false); - - // Aligned at 4 byte boundary. - offset += ((moduleLength + 3) & ~3) + 4; - } -#undef __SAFEADD__ - } - else { - // Remote debug executor loads script from a Uri, rather than taking the actual bundle string - m_innerInstance->loadScriptFromString( - std::make_unique(m_devSettings->useWebDebugger ? bundleUrl : jsBundleString), + while (offset < bufferLength) { + uint32_t segment; + __SAFEADD__(offset, 4, segment) + uint32_t moduleLength = (bufferLength < segment) ? 0 : *(((uint32_t *)buffer) + offset / 4); + + // Early break if the module length is computed as 0.. as the segment start may be overflowing the buffer. + if (moduleLength == 0) + break; + + uint32_t segmentEnd; + __SAFEADD__(moduleLength, segment, segmentEnd) + // Early break if the segment overflows beyond the buffer. This is unlikely for a properly formatted + // HBCB though. + if (segmentEnd > bufferLength) + break; + + m_innerInstance->loadScriptFromString( + std::make_unique(std::string(buffer + segment, buffer + segmentEnd)), bundleUrl, - synchronously); - } - } - else { -#if (defined(_MSC_VER) && !defined(WINRT)) - std::string bundlePath = (fs::path(m_devSettings->bundleRootPath) / jsBundleRelativePath).string(); - auto bundleString = FileMappingBigString::fromPath(bundlePath); -#else - std::string bundlePath; - if (m_devSettings->bundleRootPath._Starts_with("resource://")) { - auto uri = winrt::Windows::Foundation::Uri( - winrt::to_hstring(m_devSettings->bundleRootPath), winrt::to_hstring(jsBundleRelativePath)); - bundlePath = winrt::to_string(uri.ToString()); - } - else { - bundlePath = (fs::path(m_devSettings->bundleRootPath) / (jsBundleRelativePath + ".bundle")).string(); - } + false); - auto bundleString = std::make_unique<::Microsoft::ReactNative::StorageFileBigString>(bundlePath); -#endif - m_innerInstance->loadScriptFromString(std::move(bundleString), std::move(jsBundleRelativePath), synchronously); + // Aligned at 4 byte boundary. + offset += ((moduleLength + 3) & ~3) + 4; } +#undef __SAFEADD__ + } else { + // Remote debug executor loads script from a Uri, rather than taking the actual bundle string + m_innerInstance->loadScriptFromString( + std::make_unique(m_devSettings->useWebDebugger ? bundleUrl : jsBundleString), + bundleUrl, + synchronously); } - catch (const std::exception& e) { - m_devSettings->errorCallback(e.what()); + } else { +#if (defined(_MSC_VER) && !defined(WINRT)) + std::string bundlePath = (fs::path(m_devSettings->bundleRootPath) / jsBundleRelativePath).string(); + auto bundleString = FileMappingBigString::fromPath(bundlePath); +#else + std::string bundlePath; + if (m_devSettings->bundleRootPath._Starts_with("resource://")) { + auto uri = winrt::Windows::Foundation::Uri( + winrt::to_hstring(m_devSettings->bundleRootPath), winrt::to_hstring(jsBundleRelativePath)); + bundlePath = winrt::to_string(uri.ToString()); + } else { + bundlePath = (fs::path(m_devSettings->bundleRootPath) / (jsBundleRelativePath + ".bundle")).string(); } - catch (const winrt::hresult_error& hrerr) { - auto error = fmt::format("[0x{:0>8x}] {}", static_cast(hrerr.code()), winrt::to_string(hrerr.message())); - m_devSettings->errorCallback(std::move(error)); - } + auto bundleString = std::make_unique<::Microsoft::ReactNative::StorageFileBigString>(bundlePath); +#endif + m_innerInstance->loadScriptFromString(std::move(bundleString), std::move(jsBundleRelativePath), synchronously); } + } catch (const std::exception &e) { + m_devSettings->errorCallback(e.what()); + } catch (const winrt::hresult_error &hrerr) { + auto error = fmt::format("[0x{:0>8x}] {}", static_cast(hrerr.code()), winrt::to_string(hrerr.message())); - InstanceImpl::~InstanceImpl() { - if (m_devSettings->jsiEngineOverride == JSIEngineOverride::Hermes) { - m_devManager->StopInspector(); - } - m_nativeQueue->quitSynchronous(); - } + m_devSettings->errorCallback(std::move(error)); + } +} - std::vector> InstanceImpl::GetDefaultNativeModules( - std::shared_ptr nativeQueue) { - std::vector> modules; - auto transitionalProps{ ReactPropertyBagHelper::CreatePropertyBag() }; - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetHttpModuleName(), - [nativeQueue, transitionalProps]() -> std::unique_ptr { - return Microsoft::React::CreateHttpModule(transitionalProps); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetWebSocketModuleName(), - [nativeQueue, transitionalProps]() -> std::unique_ptr { - return Microsoft::React::CreateWebSocketModule(transitionalProps); - }, - nativeQueue)); - - // TODO: This is not included for UWP because we have a different module which - // is added later. However, this one is designed - // so that we can base a UWP version on it. We need to do that but is not high - // priority. +InstanceImpl::~InstanceImpl() { + if (m_devSettings->jsiEngineOverride == JSIEngineOverride::Hermes) { + m_devManager->StopInspector(); + } + m_nativeQueue->quitSynchronous(); +} + +std::vector> InstanceImpl::GetDefaultNativeModules( + std::shared_ptr nativeQueue) { + std::vector> modules; + auto transitionalProps{ReactPropertyBagHelper::CreatePropertyBag()}; + + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetHttpModuleName(), + [nativeQueue, transitionalProps]() -> std::unique_ptr { + return Microsoft::React::CreateHttpModule(transitionalProps); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetWebSocketModuleName(), + [nativeQueue, transitionalProps]() -> std::unique_ptr { + return Microsoft::React::CreateWebSocketModule(transitionalProps); + }, + nativeQueue)); + + // TODO: This is not included for UWP because we have a different module which + // is added later. However, this one is designed + // so that we can base a UWP version on it. We need to do that but is not high + // priority. #if (defined(_MSC_VER) && !defined(WINRT)) - modules.push_back(std::make_unique( - m_innerInstance, - "Timing", - [nativeQueue]() -> std::unique_ptr { return react::CreateTimingModule(nativeQueue); }, - nativeQueue)); + modules.push_back(std::make_unique( + m_innerInstance, + "Timing", + [nativeQueue]() -> std::unique_ptr { return react::CreateTimingModule(nativeQueue); }, + nativeQueue)); #endif - uint32_t hermesBytecodeVersion = 0; + uint32_t hermesBytecodeVersion = 0; #if defined(USE_HERMES) && defined(ENABLE_DEVSERVER_HBCBUNDLES) - hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; + hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; #endif - // TODO - Encapsulate this in a helpers, and make sure callers add it to their - // list - std::string bundleUrl = (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback) - ? DevServerHelper::get_BundleUrl( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath, - m_devSettings->platformName, - true /*dev*/, - m_devSettings->useFastRefresh, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion) - : std::string(); - modules.push_back(std::make_unique( - m_innerInstance, - facebook::react::SourceCodeModule::Name, - [bundleUrl]() -> std::unique_ptr { - return std::make_unique(bundleUrl); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - "ExceptionsManager", - [redboxHandler = m_devSettings->redboxHandler]() mutable { - return std::make_unique(redboxHandler); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - PlatformConstantsModule::Name, - []() { return std::make_unique(); }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - StatusBarManagerModule::Name, - []() { return std::make_unique(); }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetBlobModuleName(), - [transitionalProps]() { return Microsoft::React::CreateBlobModule(transitionalProps); }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetFileReaderModuleName(), - [transitionalProps]() { return Microsoft::React::CreateFileReaderModule(transitionalProps); }, - nativeQueue)); - - return modules; - } - - void InstanceImpl::RegisterForReloadIfNecessary() noexcept { - // setup polling for live reload - if (!m_isInError && !m_devSettings->useFastRefresh && m_devSettings->liveReloadCallback != nullptr) { - m_devManager->StartPollingLiveReload( - m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort, m_devSettings->liveReloadCallback); - } - } + // TODO - Encapsulate this in a helpers, and make sure callers add it to their + // list + std::string bundleUrl = (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback) + ? DevServerHelper::get_BundleUrl( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath, + m_devSettings->platformName, + true /*dev*/, + m_devSettings->useFastRefresh, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion) + : std::string(); + modules.push_back(std::make_unique( + m_innerInstance, + facebook::react::SourceCodeModule::Name, + [bundleUrl]() -> std::unique_ptr { + return std::make_unique(bundleUrl); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + "ExceptionsManager", + [redboxHandler = m_devSettings->redboxHandler]() mutable { + return std::make_unique(redboxHandler); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + PlatformConstantsModule::Name, + []() { return std::make_unique(); }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + StatusBarManagerModule::Name, + []() { return std::make_unique(); }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetBlobModuleName(), + [transitionalProps]() { return Microsoft::React::CreateBlobModule(transitionalProps); }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetFileReaderModuleName(), + [transitionalProps]() { return Microsoft::React::CreateFileReaderModule(transitionalProps); }, + nativeQueue)); + + return modules; +} + +void InstanceImpl::RegisterForReloadIfNecessary() noexcept { + // setup polling for live reload + if (!m_isInError && !m_devSettings->useFastRefresh && m_devSettings->liveReloadCallback != nullptr) { + m_devManager->StartPollingLiveReload( + m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort, m_devSettings->liveReloadCallback); + } +} - void InstanceImpl::DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic&& eventData) { - if (m_isInError) { - return; - } +void InstanceImpl::DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic &&eventData) { + if (m_isInError) { + return; + } - folly::dynamic params = folly::dynamic::array(viewTag, eventName, std::move(eventData)); - m_innerInstance->callJSFunction("RCTEventEmitter", "receiveEvent", std::move(params)); - } + folly::dynamic params = folly::dynamic::array(viewTag, eventName, std::move(eventData)); + m_innerInstance->callJSFunction("RCTEventEmitter", "receiveEvent", std::move(params)); +} - void InstanceImpl::invokeCallback(const int64_t callbackId, folly::dynamic&& params) { - if (m_isInError) { - return; - } +void InstanceImpl::invokeCallback(const int64_t callbackId, folly::dynamic &¶ms) { + if (m_isInError) { + return; + } - m_innerInstance->callJSCallback(callbackId, std::move(params)); - } + m_innerInstance->callJSCallback(callbackId, std::move(params)); +} - } // namespace react +} // namespace react } // namespace facebook diff --git a/vnext/Shared/OInstance.h b/vnext/Shared/OInstance.h index a85bad13e24..ff4e013ef3a 100644 --- a/vnext/Shared/OInstance.h +++ b/vnext/Shared/OInstance.h @@ -18,87 +18,87 @@ #include namespace facebook { - namespace react { - - struct IDevSupportManager; - struct IReactRootView; - - class InstanceImpl final : public InstanceWrapper, private ::std::enable_shared_from_this { - public: - static std::shared_ptr MakeNoBundle( - std::shared_ptr&& instance, - std::string&& jsBundleBasePath, - std::vector< - std::tuple>> - && cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr&& callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept; - - static std::shared_ptr MakeAndLoadBundle( - std::shared_ptr&& instance, - std::string&& jsBundleBasePath, - std::string&& jsBundleRelativePath, - std::vector< - std::tuple>> - && cxxModules, - std::shared_ptr turboModuleRegistry, - std::unique_ptr&& callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept; - - // Instance methods - void loadBundle(std::string&& jsBundleRelativePath) override; - void loadBundleSync(std::string&& jsBundleRelativePath) override; - virtual const std::shared_ptr& GetInstance() const noexcept override { - return m_innerInstance; - } - - void DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic&& eventData) override; - void invokeCallback(const int64_t callbackId, folly::dynamic&& params) override; - - ~InstanceImpl(); - - private: - InstanceImpl( - std::shared_ptr&& instance, - std::string&& jsBundleFile, - std::vector< - std::tuple>> - && cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr&& callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager); - - std::vector> GetDefaultNativeModules(std::shared_ptr nativeQueue); - void RegisterForReloadIfNecessary() noexcept; - void loadBundleInternal(std::string&& jsBundleRelativePath, bool synchronously); - void SetInError() noexcept; - - private: - std::shared_ptr m_innerInstance; - - std::string m_jsBundleBasePath; - std::shared_ptr m_moduleRegistry; - std::shared_ptr m_turboModuleRegistry; - std::shared_ptr m_longLivedObjectCollection; - std::shared_ptr m_jsThread; - std::shared_ptr m_nativeQueue; - - std::shared_ptr m_devManager; - std::shared_ptr m_devSettings; - bool m_isInError{ false }; - }; - - } // namespace react +namespace react { + +struct IDevSupportManager; +struct IReactRootView; + +class InstanceImpl final : public InstanceWrapper, private ::std::enable_shared_from_this { + public: + static std::shared_ptr MakeNoBundle( + std::shared_ptr &&instance, + std::string &&jsBundleBasePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept; + + static std::shared_ptr MakeAndLoadBundle( + std::shared_ptr &&instance, + std::string &&jsBundleBasePath, + std::string &&jsBundleRelativePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept; + + // Instance methods + void loadBundle(std::string &&jsBundleRelativePath) override; + void loadBundleSync(std::string &&jsBundleRelativePath) override; + virtual const std::shared_ptr &GetInstance() const noexcept override { + return m_innerInstance; + } + + void DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic &&eventData) override; + void invokeCallback(const int64_t callbackId, folly::dynamic &¶ms) override; + + ~InstanceImpl(); + + private: + InstanceImpl( + std::shared_ptr &&instance, + std::string &&jsBundleFile, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager); + + std::vector> GetDefaultNativeModules(std::shared_ptr nativeQueue); + void RegisterForReloadIfNecessary() noexcept; + void loadBundleInternal(std::string &&jsBundleRelativePath, bool synchronously); + void SetInError() noexcept; + + private: + std::shared_ptr m_innerInstance; + + std::string m_jsBundleBasePath; + std::shared_ptr m_moduleRegistry; + std::shared_ptr m_turboModuleRegistry; + std::shared_ptr m_longLivedObjectCollection; + std::shared_ptr m_jsThread; + std::shared_ptr m_nativeQueue; + + std::shared_ptr m_devManager; + std::shared_ptr m_devSettings; + bool m_isInError{false}; +}; + +} // namespace react } // namespace facebook diff --git a/vnext/Shared/RuntimeOptions.cpp b/vnext/Shared/RuntimeOptions.cpp index b443eb94ad3..f581ffe2309 100644 --- a/vnext/Shared/RuntimeOptions.cpp +++ b/vnext/Shared/RuntimeOptions.cpp @@ -13,89 +13,89 @@ using std::scoped_lock; using std::string; namespace { - std::unordered_map g_runtimeOptionInts; - std::unordered_map g_runtimeOptionStrings; - mutex g_runtimeOptionsMutex; +std::unordered_map g_runtimeOptionInts; +std::unordered_map g_runtimeOptionStrings; +mutex g_runtimeOptionsMutex; - void __cdecl GetStringCallback(const char* buffer, size_t length, void* state) { - if (!buffer) - return; +void __cdecl GetStringCallback(const char *buffer, size_t length, void *state) { + if (!buffer) + return; - *static_cast(state) = static_cast(malloc(length)); - strncpy_s(*static_cast(state), length, buffer, length); - } + *static_cast(state) = static_cast(malloc(length)); + strncpy_s(*static_cast(state), length, buffer, length); +} } // namespace namespace Microsoft::React { - void __cdecl SetRuntimeOptionBool(string&& name, bool value) noexcept { - MicrosoftReactSetRuntimeOptionBool(name.c_str(), value); - } +void __cdecl SetRuntimeOptionBool(string &&name, bool value) noexcept { + MicrosoftReactSetRuntimeOptionBool(name.c_str(), value); +} - void __cdecl SetRuntimeOptionInt(string&& name, int32_t value) noexcept { - MicrosoftReactSetRuntimeOptionInt(name.c_str(), value); - } +void __cdecl SetRuntimeOptionInt(string &&name, int32_t value) noexcept { + MicrosoftReactSetRuntimeOptionInt(name.c_str(), value); +} - void __cdecl SetRuntimeOptionString(string&& name, string&& value) noexcept { - if (!value.empty()) - MicrosoftReactSetRuntimeOptionString(std::move(name).c_str(), std::move(value).c_str()); - } +void __cdecl SetRuntimeOptionString(string &&name, string &&value) noexcept { + if (!value.empty()) + MicrosoftReactSetRuntimeOptionString(std::move(name).c_str(), std::move(value).c_str()); +} - const bool __cdecl GetRuntimeOptionBool(const string& name) noexcept { - return MicrosoftReactGetRuntimeOptionBool(name.c_str()); - } +const bool __cdecl GetRuntimeOptionBool(const string &name) noexcept { + return MicrosoftReactGetRuntimeOptionBool(name.c_str()); +} - const int32_t __cdecl GetRuntimeOptionInt(const string& name) noexcept { - return MicrosoftReactGetRuntimeOptionInt(name.c_str()); - } +const int32_t __cdecl GetRuntimeOptionInt(const string &name) noexcept { + return MicrosoftReactGetRuntimeOptionInt(name.c_str()); +} - const string __cdecl GetRuntimeOptionString(const string& name) noexcept { - char* payload{ nullptr }; - MicrosoftReactGetRuntimeOptionString(name.c_str(), GetStringCallback, &payload); +const string __cdecl GetRuntimeOptionString(const string &name) noexcept { + char *payload{nullptr}; + MicrosoftReactGetRuntimeOptionString(name.c_str(), GetStringCallback, &payload); - if (!payload) - return string{}; + if (!payload) + return string{}; - return string{ std::move(payload) }; - } + return string{std::move(payload)}; +} } // namespace Microsoft::React -void __cdecl MicrosoftReactSetRuntimeOptionBool(const char* name, bool value) noexcept { +void __cdecl MicrosoftReactSetRuntimeOptionBool(const char *name, bool value) noexcept { if (!name) return; - scoped_lock lock{ g_runtimeOptionsMutex }; + scoped_lock lock{g_runtimeOptionsMutex}; if (value) g_runtimeOptionInts.insert_or_assign(name, 1); else g_runtimeOptionInts.erase(name); } -void __cdecl MicrosoftReactSetRuntimeOptionInt(const char* name, int32_t value) noexcept { +void __cdecl MicrosoftReactSetRuntimeOptionInt(const char *name, int32_t value) noexcept { if (!name) return; - scoped_lock lock{ g_runtimeOptionsMutex }; + scoped_lock lock{g_runtimeOptionsMutex}; if (value) g_runtimeOptionInts.insert_or_assign(name, value); else g_runtimeOptionInts.erase(name); } -void __cdecl MicrosoftReactSetRuntimeOptionString(const char* name, const char* value) noexcept { +void __cdecl MicrosoftReactSetRuntimeOptionString(const char *name, const char *value) noexcept { if (!name) return; - scoped_lock lock{ g_runtimeOptionsMutex }; + scoped_lock lock{g_runtimeOptionsMutex}; if (value) g_runtimeOptionStrings.insert_or_assign(name, value); else g_runtimeOptionStrings.erase(name); } -const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char* name) noexcept { - scoped_lock lock{ g_runtimeOptionsMutex }; +const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept { + scoped_lock lock{g_runtimeOptionsMutex}; auto itr = g_runtimeOptionInts.find(name); if (itr != g_runtimeOptionInts.end()) return itr->second != 0; @@ -103,8 +103,8 @@ const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char* name) noexcept return false; } -const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char* name) noexcept { - scoped_lock lock{ g_runtimeOptionsMutex }; +const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexcept { + scoped_lock lock{g_runtimeOptionsMutex}; auto itr = g_runtimeOptionInts.find(name); if (itr != g_runtimeOptionInts.end()) return itr->second; @@ -112,13 +112,12 @@ const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char* name) noexce return 0; } -void MicrosoftReactGetRuntimeOptionString(const char* name, MicrosoftReactGetStringCallback callBack, void* state) { - scoped_lock lock{ g_runtimeOptionsMutex }; +void MicrosoftReactGetRuntimeOptionString(const char *name, MicrosoftReactGetStringCallback callBack, void *state) { + scoped_lock lock{g_runtimeOptionsMutex}; auto itr = g_runtimeOptionStrings.find(name); if (itr != g_runtimeOptionStrings.cend()) { callBack(itr->second.c_str(), itr->second.size() * sizeof(char) + 1 /*NULL termination*/, state); - } - else { + } else { callBack(nullptr, 0, state); } } diff --git a/vnext/Shared/RuntimeOptions.h b/vnext/Shared/RuntimeOptions.h index a9b4c87e7ec..80cc9111dd8 100644 --- a/vnext/Shared/RuntimeOptions.h +++ b/vnext/Shared/RuntimeOptions.h @@ -7,38 +7,38 @@ /// Sets a global boolean value identified by an arbitrary string. /// /// Global key -void __cdecl MicrosoftReactSetRuntimeOptionBool(const char* name, bool value) noexcept; +void __cdecl MicrosoftReactSetRuntimeOptionBool(const char *name, bool value) noexcept; /// /// Sets a global signed integer value identified by an arbitrary string. /// /// Global boolean key -void __cdecl MicrosoftReactSetRuntimeOptionInt(const char* name, int32_t value) noexcept; +void __cdecl MicrosoftReactSetRuntimeOptionInt(const char *name, int32_t value) noexcept; /// /// Sets a global signed integer value identified by an arbitrary string. /// /// Global string key -void __cdecl MicrosoftReactSetRuntimeOptionString(const char* name, const char* value) noexcept; +void __cdecl MicrosoftReactSetRuntimeOptionString(const char *name, const char *value) noexcept; /// /// Retrieves a global boolean value for the given key. /// /// Global boolean key /// Value stored for the given key, or false if the entry doesn't exist (default) -const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char* name) noexcept; +const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept; /// /// Retrieves a global boolean value for the given key. /// /// Global key /// Value stored for the given key, or 0 if the entry doesn't exist (default) -const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char* name) noexcept; +const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexcept; /// String contents. nullptr if none found /// String length. 0 if none found /// Pointer used to pass or retrieve arbitrary data -typedef void(__cdecl* MicrosoftReactGetStringCallback)(const char* buffer, size_t length, void* state); +typedef void(__cdecl *MicrosoftReactGetStringCallback)(const char *buffer, size_t length, void *state); /// /// Retrieves a global string value for the given key. @@ -46,4 +46,4 @@ typedef void(__cdecl* MicrosoftReactGetStringCallback)(const char* buffer, size_ /// Global key /// Handler used to access the obtained string /// Arbitrary data to pass on to or retrieve from callBack -void MicrosoftReactGetRuntimeOptionString(const char* name, MicrosoftReactGetStringCallback callBack, void* state); +void MicrosoftReactGetRuntimeOptionString(const char *name, MicrosoftReactGetStringCallback callBack, void *state); From d6c1318a36b314ada6e926aae688642230ab1124 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 17:03:31 -0700 Subject: [PATCH 19/23] Use global.FileReader --- vnext/src/IntegrationTests/BlobTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnext/src/IntegrationTests/BlobTest.js b/vnext/src/IntegrationTests/BlobTest.js index cc04855ca51..c104386ab2d 100644 --- a/vnext/src/IntegrationTests/BlobTest.js +++ b/vnext/src/IntegrationTests/BlobTest.js @@ -178,7 +178,7 @@ class BlobTest extends React.Component<{...}, State> { componentDidMount() { this._get(); this._waitFor(this._getSucceeded, 6, (doneSucceeded) => { - let reader = new FileReader(); + let reader = new global.FileReader(); reader.readAsDataURL(this.state.xhr.response); reader.onload = () => { TestModule.markTestPassed( From 748268b074be3e538a28ed57b1a329cf22f76cce Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 17:17:57 -0700 Subject: [PATCH 20/23] Remove duplicate JS override --- vnext/overrides.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vnext/overrides.json b/vnext/overrides.json index f37c7ed5ce9..3419192013f 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -90,10 +90,6 @@ "type": "platform", "file": "src/IntegrationTests/BlobTest.js" }, - { - "type": "platform", - "file": "src/IntegrationTests/BlobTest.js" - }, { "type": "platform", "file": "src/IntegrationTests/DummyTest.js" From 572b37414c69fc29f92a270162447cd9b9ba45c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Sat, 25 Jun 2022 23:11:44 -0700 Subject: [PATCH 21/23] Enable Blob module in UWP (#10187) * Update packages.lock.json * Update packages.lock.json * Add Shared project to ReactUWPTestApp solution * RNTesterApp.csproj formatting * Enable Blob module in UWP * Change files * Update packages.lock.json * Use context property bag for runtime options in MSRN * Remove unused options header * Revert ReactUWPTestApp.sln * Update packages.lock.json * Update packages.lock.json * Use namespace in monolith HTTP module property --- .../windows/RNTesterApp/RNTesterApp.csproj | 4 +- .../Base/CoreNativeModules.cpp | 25 + vnext/Shared/OInstance.cpp | 1062 +++++++++-------- 3 files changed, 569 insertions(+), 522 deletions(-) diff --git a/packages/e2e-test-app/windows/RNTesterApp/RNTesterApp.csproj b/packages/e2e-test-app/windows/RNTesterApp/RNTesterApp.csproj index 8d7c83cd37f..ffb60d4bb8b 100644 --- a/packages/e2e-test-app/windows/RNTesterApp/RNTesterApp.csproj +++ b/packages/e2e-test-app/windows/RNTesterApp/RNTesterApp.csproj @@ -145,7 +145,7 @@ - + 6.2.9 @@ -165,4 +165,4 @@ - + \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp b/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp index a2a2c9d3b36..9522dc4b59a 100644 --- a/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp +++ b/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp @@ -19,8 +19,12 @@ namespace Microsoft::ReactNative { +using winrt::Microsoft::ReactNative::ReactPropertyBag; + namespace { +using winrt::Microsoft::ReactNative::ReactPropertyId; + bool HasPackageIdentity() noexcept { static const bool hasPackageIdentity = []() noexcept { auto packageStatics = winrt::get_activation_factory( @@ -35,6 +39,13 @@ bool HasPackageIdentity() noexcept { return hasPackageIdentity; } +ReactPropertyId HttpUseMonolithicModuleProperty() noexcept { + static ReactPropertyId propId{ + L"ReactNative.Http" + L"UseMonolithicModule"}; + return propId; +} + } // namespace std::vector GetCoreModules( @@ -50,11 +61,25 @@ std::vector GetCoreModules( [props = context->Properties()]() { return Microsoft::React::CreateHttpModule(props); }, jsMessageQueue); + if (!ReactPropertyBag(context->Properties()).Get(HttpUseMonolithicModuleProperty())) { + 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); + } + modules.emplace_back( "Timing", [batchingUIMessageQueue]() { return facebook::react::CreateTimingModule(batchingUIMessageQueue); }, batchingUIMessageQueue); + // Note: `context` is moved to remove the reference from the current scope. + // This should either be the last usage of `context`, or the std::move call should happen later in this method. modules.emplace_back( NativeAnimatedModule::name, [context = std::move(context)]() mutable { return std::make_unique(std::move(context)); }, diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index 6054526c272..05c4a3b9ba4 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -71,185 +71,187 @@ using winrt::Microsoft::ReactNative::ReactPropertyBagHelper; namespace Microsoft::React { -/*extern*/ std::unique_ptr CreateHttpModule( - winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept { - if (GetRuntimeOptionBool("Http.UseMonolithicModule")) { - return std::make_unique(); - } else { - return std::make_unique(inspectableProperties); + /*extern*/ std::unique_ptr CreateHttpModule( + winrt::Windows::Foundation::IInspectable const& inspectableProperties) noexcept { + if (GetRuntimeOptionBool("Http.UseMonolithicModule")) { + return std::make_unique(); + } + else { + return std::make_unique(inspectableProperties); + } } -} } // namespace Microsoft::React namespace facebook { -namespace react { - -namespace { - -class OJSIExecutorFactory : public JSExecutorFactory { - public: - std::unique_ptr createJSExecutor( - std::shared_ptr delegate, - std::shared_ptr jsQueue) override { - Logger logger; - if (loggingHook_) { - // TODO :: Ensure the logLevels are mapped properly. - logger = [loggingHook = std::move(loggingHook_)](const std::string &message, unsigned int logLevel) { - loggingHook(static_cast(logLevel), message.c_str()); - }; - } else { - logger = [loggingHook = std::move(loggingHook_)](const std::string & /*message*/, unsigned int /*logLevel*/) {}; - } - bindNativeLogger(*runtimeHolder_->getRuntime(), logger); + namespace react { + + namespace { + + class OJSIExecutorFactory : public JSExecutorFactory { + public: + std::unique_ptr createJSExecutor( + std::shared_ptr delegate, + std::shared_ptr jsQueue) override { + Logger logger; + if (loggingHook_) { + // TODO :: Ensure the logLevels are mapped properly. + logger = [loggingHook = std::move(loggingHook_)](const std::string& message, unsigned int logLevel) { + loggingHook(static_cast(logLevel), message.c_str()); + }; + } + else { + logger = [loggingHook = std::move(loggingHook_)](const std::string& /*message*/, unsigned int /*logLevel*/) {}; + } + bindNativeLogger(*runtimeHolder_->getRuntime(), logger); - auto turboModuleManager = std::make_shared(turboModuleRegistry_, jsCallInvoker_); + auto turboModuleManager = std::make_shared(turboModuleRegistry_, jsCallInvoker_); - // TODO: The binding here should also add the proxys that convert cxxmodules into turbomodules - // [vmoroz] Note, that we must not use the RN TurboCxxModule.h code because it uses global LongLivedObjectCollection - // instance that prevents us from using multiple RN instance in the same process. - auto binding = [turboModuleManager](const std::string &name) -> std::shared_ptr { - return turboModuleManager->getModule(name); - }; + // TODO: The binding here should also add the proxys that convert cxxmodules into turbomodules + // [vmoroz] Note, that we must not use the RN TurboCxxModule.h code because it uses global LongLivedObjectCollection + // instance that prevents us from using multiple RN instance in the same process. + auto binding = [turboModuleManager](const std::string& name) -> std::shared_ptr { + return turboModuleManager->getModule(name); + }; - TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding), longLivedObjectCollection_); + TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding), longLivedObjectCollection_); - // init TurboModule - for (const auto &moduleName : turboModuleManager->getEagerInitModuleNames()) { - turboModuleManager->getModule(moduleName); - } + // init TurboModule + for (const auto& moduleName : turboModuleManager->getEagerInitModuleNames()) { + turboModuleManager->getModule(moduleName); + } - return std::make_unique( - runtimeHolder_->getRuntime(), - std::move(delegate), - JSIExecutor::defaultTimeoutInvoker, - [isProfiling = isProfilingEnabled_]([[maybe_unused]] jsi::Runtime &runtime) { + return std::make_unique( + runtimeHolder_->getRuntime(), + std::move(delegate), + JSIExecutor::defaultTimeoutInvoker, + [isProfiling = isProfilingEnabled_]([[maybe_unused]] jsi::Runtime& runtime) { #ifdef ENABLE_JS_SYSTRACE_TO_ETW - facebook::react::tracing::initializeJSHooks(runtime, isProfiling); + facebook::react::tracing::initializeJSHooks(runtime, isProfiling); #endif - }); - } + }); + } + + OJSIExecutorFactory( + std::shared_ptr runtimeHolder, + NativeLoggingHook loggingHook, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + bool isProfilingEnabled, + std::shared_ptr jsCallInvoker) noexcept + : runtimeHolder_{ std::move(runtimeHolder) }, + loggingHook_{ std::move(loggingHook) }, + turboModuleRegistry_{ std::move(turboModuleRegistry) }, + longLivedObjectCollection_{ std::move(longLivedObjectCollection) }, + jsCallInvoker_{ std::move(jsCallInvoker) }, + isProfilingEnabled_{ isProfilingEnabled } {} + + private: + std::shared_ptr runtimeHolder_; + std::shared_ptr turboModuleRegistry_; + std::shared_ptr longLivedObjectCollection_; + std::shared_ptr jsCallInvoker_; + NativeLoggingHook loggingHook_; + bool isProfilingEnabled_; + }; + + } // namespace + + void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const char* /*tag*/) {} + + /*static*/ std::shared_ptr InstanceImpl::MakeNoBundle( + std::shared_ptr&& instance, + std::string&& jsBundleBasePath, + std::vector< + std::tuple>> + && cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr&& callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept { + auto inner = std::shared_ptr(new InstanceImpl( + std::move(instance), + std::move(jsBundleBasePath), + std::move(cxxModules), + std::move(turboModuleRegistry), + std::move(longLivedObjectCollection), + std::move(callback), + std::move(jsQueue), + std::move(nativeQueue), + std::move(devSettings), + std::move(devManager))); + + inner->RegisterForReloadIfNecessary(); + + return inner; + } + + /*static*/ std::shared_ptr InstanceImpl::MakeAndLoadBundle( + std::shared_ptr&& instance, + std::string&& jsBundleBasePath, + std::string&& jsBundleRelativePath, + std::vector< + std::tuple>> + && cxxModules, + std::shared_ptr turboModuleRegistry, + std::unique_ptr&& callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept { + auto inner = std::shared_ptr(new InstanceImpl( + std::move(instance), + std::move(jsBundleBasePath), + std::move(cxxModules), + std::move(turboModuleRegistry), + nullptr, + std::move(callback), + std::move(jsQueue), + std::move(nativeQueue), + std::move(devSettings), + std::move(devManager))); + + inner->loadBundle(std::move(jsBundleRelativePath)); + inner->RegisterForReloadIfNecessary(); + + return inner; + } - OJSIExecutorFactory( - std::shared_ptr runtimeHolder, - NativeLoggingHook loggingHook, + void InstanceImpl::SetInError() noexcept { + m_isInError = true; + } + + namespace { + bool shouldStartHermesInspector(DevSettings& devSettings) { + bool isHermes = + ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || + (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && + devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); + + if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) + return true; + else + return false; + } + } // namespace + + InstanceImpl::InstanceImpl( + std::shared_ptr&& instance, + std::string&& jsBundleBasePath, + std::vector< + std::tuple>> + && cxxModules, std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - bool isProfilingEnabled, - std::shared_ptr jsCallInvoker) noexcept - : runtimeHolder_{std::move(runtimeHolder)}, - loggingHook_{std::move(loggingHook)}, - turboModuleRegistry_{std::move(turboModuleRegistry)}, - longLivedObjectCollection_{std::move(longLivedObjectCollection)}, - jsCallInvoker_{std::move(jsCallInvoker)}, - isProfilingEnabled_{isProfilingEnabled} {} - - private: - std::shared_ptr runtimeHolder_; - std::shared_ptr turboModuleRegistry_; - std::shared_ptr longLivedObjectCollection_; - std::shared_ptr jsCallInvoker_; - NativeLoggingHook loggingHook_; - bool isProfilingEnabled_; -}; - -} // namespace - -void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const char * /*tag*/) {} - -/*static*/ std::shared_ptr InstanceImpl::MakeNoBundle( - std::shared_ptr &&instance, - std::string &&jsBundleBasePath, - std::vector< - std::tuple>> - &&cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept { - auto inner = std::shared_ptr(new InstanceImpl( - std::move(instance), - std::move(jsBundleBasePath), - std::move(cxxModules), - std::move(turboModuleRegistry), - std::move(longLivedObjectCollection), - std::move(callback), - std::move(jsQueue), - std::move(nativeQueue), - std::move(devSettings), - std::move(devManager))); - - inner->RegisterForReloadIfNecessary(); - - return inner; -} - -/*static*/ std::shared_ptr InstanceImpl::MakeAndLoadBundle( - std::shared_ptr &&instance, - std::string &&jsBundleBasePath, - std::string &&jsBundleRelativePath, - std::vector< - std::tuple>> - &&cxxModules, - std::shared_ptr turboModuleRegistry, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept { - auto inner = std::shared_ptr(new InstanceImpl( - std::move(instance), - std::move(jsBundleBasePath), - std::move(cxxModules), - std::move(turboModuleRegistry), - nullptr, - std::move(callback), - std::move(jsQueue), - std::move(nativeQueue), - std::move(devSettings), - std::move(devManager))); - - inner->loadBundle(std::move(jsBundleRelativePath)); - inner->RegisterForReloadIfNecessary(); - - return inner; -} - -void InstanceImpl::SetInError() noexcept { - m_isInError = true; -} - -namespace { -bool shouldStartHermesInspector(DevSettings &devSettings) { - bool isHermes = - ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || - (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && - devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); - - if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) - return true; - else - return false; -} -} // namespace - -InstanceImpl::InstanceImpl( - std::shared_ptr &&instance, - std::string &&jsBundleBasePath, - std::vector< - std::tuple>> - &&cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) - : m_turboModuleRegistry(std::move(turboModuleRegistry)), + std::shared_ptr longLivedObjectCollection, + std::unique_ptr&& callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) + : m_turboModuleRegistry(std::move(turboModuleRegistry)), m_longLivedObjectCollection(std::move(longLivedObjectCollection)), m_jsThread(std::move(jsQueue)), m_nativeQueue(nativeQueue), @@ -257,419 +259,439 @@ InstanceImpl::InstanceImpl( m_devSettings(std::move(devSettings)), m_devManager(std::move(devManager)), m_innerInstance(std::move(instance)) { - // Temp set the logmarker here - facebook::react::ReactMarker::logTaggedMarker = logMarker; + // Temp set the logmarker here + facebook::react::ReactMarker::logTaggedMarker = logMarker; #ifdef ENABLE_ETW_TRACING - // TODO :: Find a better place to initialize ETW once per process. - facebook::react::tracing::initializeETW(); + // TODO :: Find a better place to initialize ETW once per process. + facebook::react::tracing::initializeETW(); #endif - if (shouldStartHermesInspector(*m_devSettings)) { - m_devManager->StartInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort); - } - - // Default (common) NativeModules - auto modules = GetDefaultNativeModules(nativeQueue); + if (shouldStartHermesInspector(*m_devSettings)) { + m_devManager->StartInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort); + } - // Add app provided modules. - for (auto &cxxModule : cxxModules) { - modules.push_back(std::make_unique( - m_innerInstance, move(std::get<0>(cxxModule)), move(std::get<1>(cxxModule)), move(std::get<2>(cxxModule)))); - } - m_moduleRegistry = std::make_shared(std::move(modules)); - - // Choose JSExecutor - std::shared_ptr jsef; - if (m_devSettings->useWebDebugger) { - try { - auto jseFunc = m_devManager->LoadJavaScriptInProxyMode(*m_devSettings, [weakthis = weak_from_this()]() { - if (auto strongThis = weakthis.lock()) { - strongThis->SetInError(); - } - }); + // Default (common) NativeModules + auto modules = GetDefaultNativeModules(nativeQueue); - if ((jseFunc == nullptr) || m_isInError) { - m_devSettings->errorCallback("Failed to create JavaScript Executor."); - return; + // Add app provided modules. + for (auto& cxxModule : cxxModules) { + modules.push_back(std::make_unique( + m_innerInstance, move(std::get<0>(cxxModule)), move(std::get<1>(cxxModule)), move(std::get<2>(cxxModule)))); } + m_moduleRegistry = std::make_shared(std::move(modules)); + + // Choose JSExecutor + std::shared_ptr jsef; + if (m_devSettings->useWebDebugger) { + try { + auto jseFunc = m_devManager->LoadJavaScriptInProxyMode(*m_devSettings, [weakthis = weak_from_this()]() { + if (auto strongThis = weakthis.lock()) { + strongThis->SetInError(); + } + }); + + if ((jseFunc == nullptr) || m_isInError) { + m_devSettings->errorCallback("Failed to create JavaScript Executor."); + return; + } - jsef = std::make_shared(std::move(jseFunc)); - } catch (std::exception &e) { - m_devSettings->errorCallback(e.what()); - return; - } - } else { - if (m_devSettings->useFastRefresh || m_devSettings->liveReloadCallback) { - Microsoft::ReactNative::PackagerConnection::CreateOrReusePackagerConnection(*m_devSettings); - } + jsef = std::make_shared(std::move(jseFunc)); + } + catch (std::exception& e) { + m_devSettings->errorCallback(e.what()); + return; + } + } + else { + if (m_devSettings->useFastRefresh || m_devSettings->liveReloadCallback) { + Microsoft::ReactNative::PackagerConnection::CreateOrReusePackagerConnection(*m_devSettings); + } - // If the consumer gives us a JSI runtime, then use it. - if (m_devSettings->jsiRuntimeHolder) { - assert(m_devSettings->jsiEngineOverride == JSIEngineOverride::Default); - jsef = std::make_shared( - m_devSettings->jsiRuntimeHolder, - m_devSettings->loggingCallback, - m_turboModuleRegistry, - m_longLivedObjectCollection, - !m_devSettings->useFastRefresh, - m_innerInstance->getJSCallInvoker()); - } else { - assert(m_devSettings->jsiEngineOverride != JSIEngineOverride::Default); - switch (m_devSettings->jsiEngineOverride) { - case JSIEngineOverride::Hermes: - m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread); - break; - case JSIEngineOverride::V8: { + // If the consumer gives us a JSI runtime, then use it. + if (m_devSettings->jsiRuntimeHolder) { + assert(m_devSettings->jsiEngineOverride == JSIEngineOverride::Default); + jsef = std::make_shared( + m_devSettings->jsiRuntimeHolder, + m_devSettings->loggingCallback, + m_turboModuleRegistry, + m_longLivedObjectCollection, + !m_devSettings->useFastRefresh, + m_innerInstance->getJSCallInvoker()); + } + else { + assert(m_devSettings->jsiEngineOverride != JSIEngineOverride::Default); + switch (m_devSettings->jsiEngineOverride) { + case JSIEngineOverride::Hermes: + m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread); + break; + case JSIEngineOverride::V8: { #if defined(USE_V8) - std::unique_ptr scriptStore = nullptr; - std::unique_ptr preparedScriptStore = nullptr; + std::unique_ptr scriptStore = nullptr; + std::unique_ptr preparedScriptStore = nullptr; - char tempPath[MAX_PATH]; - if (GetTempPathA(MAX_PATH, tempPath)) { - preparedScriptStore = std::make_unique(tempPath); - } + char tempPath[MAX_PATH]; + if (GetTempPathA(MAX_PATH, tempPath)) { + preparedScriptStore = std::make_unique(tempPath); + } - m_devSettings->jsiRuntimeHolder = std::make_shared( + m_devSettings->jsiRuntimeHolder = std::make_shared( m_devSettings, m_jsThread, std::move(scriptStore), std::move(preparedScriptStore)); - break; + break; #else - assert(false); // V8 is not available in this build, fallthrough - [[fallthrough]]; + assert(false); // V8 is not available in this build, fallthrough + [[fallthrough]]; #endif - } - case JSIEngineOverride::V8NodeApi: { + } + case JSIEngineOverride::V8NodeApi: { #if defined(USE_V8) - std::unique_ptr preparedScriptStore; + std::unique_ptr preparedScriptStore; - wchar_t tempPath[MAX_PATH]; - if (GetTempPathW(static_cast(std::size(tempPath)), tempPath)) { - preparedScriptStore = + wchar_t tempPath[MAX_PATH]; + if (GetTempPathW(static_cast(std::size(tempPath)), tempPath)) { + preparedScriptStore = std::make_unique(winrt::to_string(tempPath)); - } + } - if (!preparedScriptStore) { - if (m_devSettings->errorCallback) - m_devSettings->errorCallback("Could not initialize prepared script store"); + if (!preparedScriptStore) { + if (m_devSettings->errorCallback) + m_devSettings->errorCallback("Could not initialize prepared script store"); - break; - } + break; + } - m_devSettings->jsiRuntimeHolder = make_shared( + m_devSettings->jsiRuntimeHolder = make_shared( m_devSettings, m_jsThread, nullptr /*scriptStore*/, std::move(preparedScriptStore)); - break; + break; #else - if (m_devSettings->errorCallback) - m_devSettings->errorCallback("JSI/V8/NAPI engine is not available in this build"); - assert(false); - [[fallthrough]]; + if (m_devSettings->errorCallback) + m_devSettings->errorCallback("JSI/V8/NAPI engine is not available in this build"); + assert(false); + [[fallthrough]]; #endif - } - case JSIEngineOverride::Chakra: - case JSIEngineOverride::ChakraCore: - default: // TODO: Add other engines once supported - m_devSettings->jsiRuntimeHolder = + } + case JSIEngineOverride::Chakra: + case JSIEngineOverride::ChakraCore: + default: // TODO: Add other engines once supported + m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread, nullptr, nullptr); - break; + break; + } + jsef = std::make_shared( + m_devSettings->jsiRuntimeHolder, + m_devSettings->loggingCallback, + m_turboModuleRegistry, + m_longLivedObjectCollection, + !m_devSettings->useFastRefresh, + m_innerInstance->getJSCallInvoker()); + } + } + + m_innerInstance->initializeBridge(std::move(callback), jsef, m_jsThread, m_moduleRegistry); + + // All JSI runtimes do support host objects and hence the native modules + // proxy. + const bool isNativeModulesProxyAvailable = ((m_devSettings->jsiRuntimeHolder != nullptr) || + (m_devSettings->jsiEngineOverride != JSIEngineOverride::Default)) && + !m_devSettings->useWebDebugger; + if (!isNativeModulesProxyAvailable) { + folly::dynamic configArray = folly::dynamic::array; + for (auto const& moduleName : m_moduleRegistry->moduleNames()) { + auto moduleConfig = m_moduleRegistry->getConfig(moduleName); + configArray.push_back(moduleConfig ? std::move(moduleConfig->config) : nullptr); + } + + folly::dynamic configs = folly::dynamic::object("remoteModuleConfig", std::move(configArray)); + m_innerInstance->setGlobalVariable( + "__fbBatchedBridgeConfig", std::make_unique(folly::toJson(configs))); } - jsef = std::make_shared( - m_devSettings->jsiRuntimeHolder, - m_devSettings->loggingCallback, - m_turboModuleRegistry, - m_longLivedObjectCollection, - !m_devSettings->useFastRefresh, - m_innerInstance->getJSCallInvoker()); } - } - m_innerInstance->initializeBridge(std::move(callback), jsef, m_jsThread, m_moduleRegistry); - - // All JSI runtimes do support host objects and hence the native modules - // proxy. - const bool isNativeModulesProxyAvailable = ((m_devSettings->jsiRuntimeHolder != nullptr) || - (m_devSettings->jsiEngineOverride != JSIEngineOverride::Default)) && - !m_devSettings->useWebDebugger; - if (!isNativeModulesProxyAvailable) { - folly::dynamic configArray = folly::dynamic::array; - for (auto const &moduleName : m_moduleRegistry->moduleNames()) { - auto moduleConfig = m_moduleRegistry->getConfig(moduleName); - configArray.push_back(moduleConfig ? std::move(moduleConfig->config) : nullptr); + void InstanceImpl::loadBundle(std::string&& jsBundleRelativePath) { + loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ false); } - folly::dynamic configs = folly::dynamic::object("remoteModuleConfig", std::move(configArray)); - m_innerInstance->setGlobalVariable( - "__fbBatchedBridgeConfig", std::make_unique(folly::toJson(configs))); - } -} - -void InstanceImpl::loadBundle(std::string &&jsBundleRelativePath) { - loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ false); -} - -void InstanceImpl::loadBundleSync(std::string &&jsBundleRelativePath) { - loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ true); -} - -// Note: Based on -// https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactCommon/cxxreact/Instance.cpp#L112 -bool isHBCBundle(const std::string &bundle) { - static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3; - - // Note:: Directly access the pointer to avoid copy/length-check. It matters as this string contains the bundle which - // can be potentially huge. - // https://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/#comment-483 - auto header = reinterpret_cast(&bundle[0]); - if (HBCBundleMagicNumber == folly::Endian::little(header->magic)) { - return true; - } else { - return false; - } -} + void InstanceImpl::loadBundleSync(std::string&& jsBundleRelativePath) { + loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ true); + } + + // Note: Based on + // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactCommon/cxxreact/Instance.cpp#L112 + bool isHBCBundle(const std::string& bundle) { + static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3; + + // Note:: Directly access the pointer to avoid copy/length-check. It matters as this string contains the bundle which + // can be potentially huge. + // https://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/#comment-483 + auto header = reinterpret_cast(&bundle[0]); + if (HBCBundleMagicNumber == folly::Endian::little(header->magic)) { + return true; + } + else { + return false; + } + } -void InstanceImpl::loadBundleInternal(std::string &&jsBundleRelativePath, bool synchronously) { - try { - if (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback != nullptr || - m_devSettings->useFastRefresh) { - // First attempt to get download the Js locally, to catch any bundling - // errors before attempting to load the actual script. + void InstanceImpl::loadBundleInternal(std::string&& jsBundleRelativePath, bool synchronously) { + try { + if (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback != nullptr || + m_devSettings->useFastRefresh) { + // First attempt to get download the Js locally, to catch any bundling + // errors before attempting to load the actual script. - uint32_t hermesBytecodeVersion = 0; + uint32_t hermesBytecodeVersion = 0; #if defined(USE_HERMES) && defined(ENABLE_DEVSERVER_HBCBUNDLES) - hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; + hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; #endif - auto [jsBundleString, success] = Microsoft::ReactNative::GetJavaScriptFromServer( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, - m_devSettings->platformName, - true /* dev */, - m_devSettings->useFastRefresh, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion); + auto [jsBundleString, success] = Microsoft::ReactNative::GetJavaScriptFromServer( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, + m_devSettings->platformName, + true /* dev */, + m_devSettings->useFastRefresh, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion); - if (!success) { - m_devManager->UpdateBundleStatus(false, -1); - m_devSettings->errorCallback(jsBundleString); - return; - } + if (!success) { + m_devManager->UpdateBundleStatus(false, -1); + m_devSettings->errorCallback(jsBundleString); + return; + } - int64_t currentTimeInMilliSeconds = - std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) - .count(); - m_devManager->UpdateBundleStatus(true, currentTimeInMilliSeconds); + int64_t currentTimeInMilliSeconds = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); + m_devManager->UpdateBundleStatus(true, currentTimeInMilliSeconds); - auto bundleUrl = DevServerHelper::get_BundleUrl( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, - m_devSettings->platformName, - /*dev*/ true, - /*hot*/ false, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion); - - // This code is based on the HBC Bundle integration on Android - // Ref: - // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp#L231 - if (isHBCBundle(jsBundleString)) { - auto script = std::make_unique(jsBundleString, false); - const char *buffer = script->c_str(); - uint32_t bufferLength = (uint32_t)script->size(); - - // Please refer the code here for details on the file format: - // https://github.com/facebook/metro/blob/b1bacf52070be62872d6bd3420f37a4405ed34e6/packages/metro/src/lib/bundleToBytecode.js#L29 - // Essentially, there is an 8 byte long file header with 4 bytes of a magic number followed by 4 bytes to encode - // the number of modules.The module buffers follows, each one starts with 4 byte header which encodes module - // length.A properly formatted HBCB should have at least 8 bytes.. - uint32_t offset = 8; + auto bundleUrl = DevServerHelper::get_BundleUrl( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, + m_devSettings->platformName, + /*dev*/ true, + /*hot*/ false, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion); + + // This code is based on the HBC Bundle integration on Android + // Ref: + // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp#L231 + if (isHBCBundle(jsBundleString)) { + auto script = std::make_unique(jsBundleString, false); + const char* buffer = script->c_str(); + uint32_t bufferLength = (uint32_t)script->size(); + + // Please refer the code here for details on the file format: + // https://github.com/facebook/metro/blob/b1bacf52070be62872d6bd3420f37a4405ed34e6/packages/metro/src/lib/bundleToBytecode.js#L29 + // Essentially, there is an 8 byte long file header with 4 bytes of a magic number followed by 4 bytes to encode + // the number of modules.The module buffers follows, each one starts with 4 byte header which encodes module + // length.A properly formatted HBCB should have at least 8 bytes.. + uint32_t offset = 8; #define __SAFEADD__(s1, s2, t) \ if (!msl::utilities::SafeAdd(s1, s2, t)) \ break; - while (offset < bufferLength) { - uint32_t segment; - __SAFEADD__(offset, 4, segment) - uint32_t moduleLength = (bufferLength < segment) ? 0 : *(((uint32_t *)buffer) + offset / 4); - - // Early break if the module length is computed as 0.. as the segment start may be overflowing the buffer. - if (moduleLength == 0) - break; - - uint32_t segmentEnd; - __SAFEADD__(moduleLength, segment, segmentEnd) - // Early break if the segment overflows beyond the buffer. This is unlikely for a properly formatted - // HBCB though. - if (segmentEnd > bufferLength) - break; - - m_innerInstance->loadScriptFromString( - std::make_unique(std::string(buffer + segment, buffer + segmentEnd)), + while (offset < bufferLength) { + uint32_t segment; + __SAFEADD__(offset, 4, segment) + uint32_t moduleLength = (bufferLength < segment) ? 0 : *(((uint32_t*)buffer) + offset / 4); + + // Early break if the module length is computed as 0.. as the segment start may be overflowing the buffer. + if (moduleLength == 0) + break; + + uint32_t segmentEnd; + __SAFEADD__(moduleLength, segment, segmentEnd) + // Early break if the segment overflows beyond the buffer. This is unlikely for a properly formatted + // HBCB though. + if (segmentEnd > bufferLength) + break; + + m_innerInstance->loadScriptFromString( + std::make_unique(std::string(buffer + segment, buffer + segmentEnd)), + bundleUrl, + false); + + // Aligned at 4 byte boundary. + offset += ((moduleLength + 3) & ~3) + 4; + } +#undef __SAFEADD__ + } + else { + // Remote debug executor loads script from a Uri, rather than taking the actual bundle string + m_innerInstance->loadScriptFromString( + std::make_unique(m_devSettings->useWebDebugger ? bundleUrl : jsBundleString), bundleUrl, - false); - - // Aligned at 4 byte boundary. - offset += ((moduleLength + 3) & ~3) + 4; + synchronously); + } } -#undef __SAFEADD__ - } else { - // Remote debug executor loads script from a Uri, rather than taking the actual bundle string - m_innerInstance->loadScriptFromString( - std::make_unique(m_devSettings->useWebDebugger ? bundleUrl : jsBundleString), - bundleUrl, - synchronously); - } - } else { + else { #if (defined(_MSC_VER) && !defined(WINRT)) - std::string bundlePath = (fs::path(m_devSettings->bundleRootPath) / jsBundleRelativePath).string(); - auto bundleString = FileMappingBigString::fromPath(bundlePath); + std::string bundlePath = (fs::path(m_devSettings->bundleRootPath) / jsBundleRelativePath).string(); + auto bundleString = FileMappingBigString::fromPath(bundlePath); #else - std::string bundlePath; - if (m_devSettings->bundleRootPath._Starts_with("resource://")) { - auto uri = winrt::Windows::Foundation::Uri( - winrt::to_hstring(m_devSettings->bundleRootPath), winrt::to_hstring(jsBundleRelativePath)); - bundlePath = winrt::to_string(uri.ToString()); - } else { - bundlePath = (fs::path(m_devSettings->bundleRootPath) / (jsBundleRelativePath + ".bundle")).string(); - } + std::string bundlePath; + if (m_devSettings->bundleRootPath._Starts_with("resource://")) { + auto uri = winrt::Windows::Foundation::Uri( + winrt::to_hstring(m_devSettings->bundleRootPath), winrt::to_hstring(jsBundleRelativePath)); + bundlePath = winrt::to_string(uri.ToString()); + } + else { + bundlePath = (fs::path(m_devSettings->bundleRootPath) / (jsBundleRelativePath + ".bundle")).string(); + } - auto bundleString = std::make_unique<::Microsoft::ReactNative::StorageFileBigString>(bundlePath); + auto bundleString = std::make_unique<::Microsoft::ReactNative::StorageFileBigString>(bundlePath); #endif - m_innerInstance->loadScriptFromString(std::move(bundleString), std::move(jsBundleRelativePath), synchronously); + m_innerInstance->loadScriptFromString(std::move(bundleString), std::move(jsBundleRelativePath), synchronously); + } + } + catch (const std::exception& e) { + m_devSettings->errorCallback(e.what()); + } + catch (const winrt::hresult_error& hrerr) { + auto error = fmt::format("[0x{:0>8x}] {}", static_cast(hrerr.code()), winrt::to_string(hrerr.message())); + + m_devSettings->errorCallback(std::move(error)); + } } - } catch (const std::exception &e) { - m_devSettings->errorCallback(e.what()); - } catch (const winrt::hresult_error &hrerr) { - auto error = fmt::format("[0x{:0>8x}] {}", static_cast(hrerr.code()), winrt::to_string(hrerr.message())); - m_devSettings->errorCallback(std::move(error)); - } -} + InstanceImpl::~InstanceImpl() { + if (m_devSettings->jsiEngineOverride == JSIEngineOverride::Hermes) { + m_devManager->StopInspector(); + } + m_nativeQueue->quitSynchronous(); + } + + std::vector> InstanceImpl::GetDefaultNativeModules( + std::shared_ptr nativeQueue) { + std::vector> modules; + auto transitionalProps{ ReactPropertyBagHelper::CreatePropertyBag() }; -InstanceImpl::~InstanceImpl() { - if (m_devSettings->jsiEngineOverride == JSIEngineOverride::Hermes) { - m_devManager->StopInspector(); - } - m_nativeQueue->quitSynchronous(); -} - -std::vector> InstanceImpl::GetDefaultNativeModules( - std::shared_ptr nativeQueue) { - std::vector> modules; - auto transitionalProps{ReactPropertyBagHelper::CreatePropertyBag()}; - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetHttpModuleName(), - [nativeQueue, transitionalProps]() -> std::unique_ptr { - return Microsoft::React::CreateHttpModule(transitionalProps); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetWebSocketModuleName(), - [nativeQueue, transitionalProps]() -> std::unique_ptr { - return Microsoft::React::CreateWebSocketModule(transitionalProps); - }, - nativeQueue)); - - // TODO: This is not included for UWP because we have a different module which - // is added later. However, this one is designed - // so that we can base a UWP version on it. We need to do that but is not high - // priority. #if (defined(_MSC_VER) && !defined(WINRT)) - modules.push_back(std::make_unique( - m_innerInstance, - "Timing", - [nativeQueue]() -> std::unique_ptr { return react::CreateTimingModule(nativeQueue); }, - nativeQueue)); + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetHttpModuleName(), + [nativeQueue, transitionalProps]() -> std::unique_ptr { + return Microsoft::React::CreateHttpModule(transitionalProps); + }, + nativeQueue)); #endif - uint32_t hermesBytecodeVersion = 0; + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetWebSocketModuleName(), + [nativeQueue, transitionalProps]() -> std::unique_ptr { + return Microsoft::React::CreateWebSocketModule(transitionalProps); + }, + nativeQueue)); + + // TODO: This is not included for UWP because we have a different module which + // is added later. However, this one is designed + // so that we can base a UWP version on it. We need to do that but is not high + // priority. +#if (defined(_MSC_VER) && !defined(WINRT)) + modules.push_back(std::make_unique( + m_innerInstance, + "Timing", + [nativeQueue]() -> std::unique_ptr { return react::CreateTimingModule(nativeQueue); }, + nativeQueue)); +#endif + + uint32_t hermesBytecodeVersion = 0; #if defined(USE_HERMES) && defined(ENABLE_DEVSERVER_HBCBUNDLES) - hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; + hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; #endif - // TODO - Encapsulate this in a helpers, and make sure callers add it to their - // list - std::string bundleUrl = (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback) - ? DevServerHelper::get_BundleUrl( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath, - m_devSettings->platformName, - true /*dev*/, - m_devSettings->useFastRefresh, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion) - : std::string(); - modules.push_back(std::make_unique( - m_innerInstance, - facebook::react::SourceCodeModule::Name, - [bundleUrl]() -> std::unique_ptr { - return std::make_unique(bundleUrl); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - "ExceptionsManager", - [redboxHandler = m_devSettings->redboxHandler]() mutable { - return std::make_unique(redboxHandler); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - PlatformConstantsModule::Name, - []() { return std::make_unique(); }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - StatusBarManagerModule::Name, - []() { return std::make_unique(); }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetBlobModuleName(), - [transitionalProps]() { return Microsoft::React::CreateBlobModule(transitionalProps); }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetFileReaderModuleName(), - [transitionalProps]() { return Microsoft::React::CreateFileReaderModule(transitionalProps); }, - nativeQueue)); - - return modules; -} - -void InstanceImpl::RegisterForReloadIfNecessary() noexcept { - // setup polling for live reload - if (!m_isInError && !m_devSettings->useFastRefresh && m_devSettings->liveReloadCallback != nullptr) { - m_devManager->StartPollingLiveReload( - m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort, m_devSettings->liveReloadCallback); - } -} + // TODO - Encapsulate this in a helpers, and make sure callers add it to their + // list + std::string bundleUrl = (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback) + ? DevServerHelper::get_BundleUrl( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath, + m_devSettings->platformName, + true /*dev*/, + m_devSettings->useFastRefresh, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion) + : std::string(); + modules.push_back(std::make_unique( + m_innerInstance, + facebook::react::SourceCodeModule::Name, + [bundleUrl]() -> std::unique_ptr { + return std::make_unique(bundleUrl); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + "ExceptionsManager", + [redboxHandler = m_devSettings->redboxHandler]() mutable { + return std::make_unique(redboxHandler); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + PlatformConstantsModule::Name, + []() { return std::make_unique(); }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + StatusBarManagerModule::Name, + []() { return std::make_unique(); }, + nativeQueue)); + + // These modules are instantiated separately in MSRN (Universal Windows). + // When there are module name colisions, the last one registered is used. + // If this code is enabled, we will have unused module instances. + // Also, MSRN has a different property bag mechanism incompatible with this method's transitionalProps variable. +#if (defined(_MSC_VER) && !defined(WINRT)) + if (Microsoft::React::GetRuntimeOptionBool("Blob.EnableModule") && + !Microsoft::React::GetRuntimeOptionBool("Http.UseMonolithicModule")) { + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetBlobModuleName(), + [transitionalProps]() { return Microsoft::React::CreateBlobModule(transitionalProps); }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetFileReaderModuleName(), + [transitionalProps]() { return Microsoft::React::CreateFileReaderModule(transitionalProps); }, + nativeQueue)); + } +#endif -void InstanceImpl::DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic &&eventData) { - if (m_isInError) { - return; - } + return modules; + } + + void InstanceImpl::RegisterForReloadIfNecessary() noexcept { + // setup polling for live reload + if (!m_isInError && !m_devSettings->useFastRefresh && m_devSettings->liveReloadCallback != nullptr) { + m_devManager->StartPollingLiveReload( + m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort, m_devSettings->liveReloadCallback); + } + } - folly::dynamic params = folly::dynamic::array(viewTag, eventName, std::move(eventData)); - m_innerInstance->callJSFunction("RCTEventEmitter", "receiveEvent", std::move(params)); -} + void InstanceImpl::DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic&& eventData) { + if (m_isInError) { + return; + } -void InstanceImpl::invokeCallback(const int64_t callbackId, folly::dynamic &¶ms) { - if (m_isInError) { - return; - } + folly::dynamic params = folly::dynamic::array(viewTag, eventName, std::move(eventData)); + m_innerInstance->callJSFunction("RCTEventEmitter", "receiveEvent", std::move(params)); + } - m_innerInstance->callJSCallback(callbackId, std::move(params)); -} + void InstanceImpl::invokeCallback(const int64_t callbackId, folly::dynamic&& params) { + if (m_isInError) { + return; + } + + m_innerInstance->callJSCallback(callbackId, std::move(params)); + } -} // namespace react -} // namespace facebook + } // namespace react + } // namespace facebook From 5c9b4e92f4fe7130526247ef3a541f0c000fc860 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 18:16:04 -0700 Subject: [PATCH 22/23] clang format --- vnext/Shared/OInstance.cpp | 1049 ++++++++++++++++++------------------ 1 file changed, 519 insertions(+), 530 deletions(-) diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index 05c4a3b9ba4..fdfbae52029 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -71,187 +71,185 @@ using winrt::Microsoft::ReactNative::ReactPropertyBagHelper; namespace Microsoft::React { - /*extern*/ std::unique_ptr CreateHttpModule( - winrt::Windows::Foundation::IInspectable const& inspectableProperties) noexcept { - if (GetRuntimeOptionBool("Http.UseMonolithicModule")) { - return std::make_unique(); - } - else { - return std::make_unique(inspectableProperties); - } +/*extern*/ std::unique_ptr CreateHttpModule( + winrt::Windows::Foundation::IInspectable const &inspectableProperties) noexcept { + if (GetRuntimeOptionBool("Http.UseMonolithicModule")) { + return std::make_unique(); + } else { + return std::make_unique(inspectableProperties); } +} } // namespace Microsoft::React namespace facebook { - namespace react { - - namespace { - - class OJSIExecutorFactory : public JSExecutorFactory { - public: - std::unique_ptr createJSExecutor( - std::shared_ptr delegate, - std::shared_ptr jsQueue) override { - Logger logger; - if (loggingHook_) { - // TODO :: Ensure the logLevels are mapped properly. - logger = [loggingHook = std::move(loggingHook_)](const std::string& message, unsigned int logLevel) { - loggingHook(static_cast(logLevel), message.c_str()); - }; - } - else { - logger = [loggingHook = std::move(loggingHook_)](const std::string& /*message*/, unsigned int /*logLevel*/) {}; - } - bindNativeLogger(*runtimeHolder_->getRuntime(), logger); - - auto turboModuleManager = std::make_shared(turboModuleRegistry_, jsCallInvoker_); - - // TODO: The binding here should also add the proxys that convert cxxmodules into turbomodules - // [vmoroz] Note, that we must not use the RN TurboCxxModule.h code because it uses global LongLivedObjectCollection - // instance that prevents us from using multiple RN instance in the same process. - auto binding = [turboModuleManager](const std::string& name) -> std::shared_ptr { - return turboModuleManager->getModule(name); - }; - - TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding), longLivedObjectCollection_); - - // init TurboModule - for (const auto& moduleName : turboModuleManager->getEagerInitModuleNames()) { - turboModuleManager->getModule(moduleName); - } - - return std::make_unique( - runtimeHolder_->getRuntime(), - std::move(delegate), - JSIExecutor::defaultTimeoutInvoker, - [isProfiling = isProfilingEnabled_]([[maybe_unused]] jsi::Runtime& runtime) { -#ifdef ENABLE_JS_SYSTRACE_TO_ETW - facebook::react::tracing::initializeJSHooks(runtime, isProfiling); -#endif - }); - } - - OJSIExecutorFactory( - std::shared_ptr runtimeHolder, - NativeLoggingHook loggingHook, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - bool isProfilingEnabled, - std::shared_ptr jsCallInvoker) noexcept - : runtimeHolder_{ std::move(runtimeHolder) }, - loggingHook_{ std::move(loggingHook) }, - turboModuleRegistry_{ std::move(turboModuleRegistry) }, - longLivedObjectCollection_{ std::move(longLivedObjectCollection) }, - jsCallInvoker_{ std::move(jsCallInvoker) }, - isProfilingEnabled_{ isProfilingEnabled } {} - - private: - std::shared_ptr runtimeHolder_; - std::shared_ptr turboModuleRegistry_; - std::shared_ptr longLivedObjectCollection_; - std::shared_ptr jsCallInvoker_; - NativeLoggingHook loggingHook_; - bool isProfilingEnabled_; +namespace react { + +namespace { + +class OJSIExecutorFactory : public JSExecutorFactory { + public: + std::unique_ptr createJSExecutor( + std::shared_ptr delegate, + std::shared_ptr jsQueue) override { + Logger logger; + if (loggingHook_) { + // TODO :: Ensure the logLevels are mapped properly. + logger = [loggingHook = std::move(loggingHook_)](const std::string &message, unsigned int logLevel) { + loggingHook(static_cast(logLevel), message.c_str()); }; + } else { + logger = [loggingHook = std::move(loggingHook_)](const std::string & /*message*/, unsigned int /*logLevel*/) {}; + } + bindNativeLogger(*runtimeHolder_->getRuntime(), logger); - } // namespace + auto turboModuleManager = std::make_shared(turboModuleRegistry_, jsCallInvoker_); - void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const char* /*tag*/) {} + // TODO: The binding here should also add the proxys that convert cxxmodules into turbomodules + // [vmoroz] Note, that we must not use the RN TurboCxxModule.h code because it uses global LongLivedObjectCollection + // instance that prevents us from using multiple RN instance in the same process. + auto binding = [turboModuleManager](const std::string &name) -> std::shared_ptr { + return turboModuleManager->getModule(name); + }; - /*static*/ std::shared_ptr InstanceImpl::MakeNoBundle( - std::shared_ptr&& instance, - std::string&& jsBundleBasePath, - std::vector< - std::tuple>> - && cxxModules, - std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr&& callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept { - auto inner = std::shared_ptr(new InstanceImpl( - std::move(instance), - std::move(jsBundleBasePath), - std::move(cxxModules), - std::move(turboModuleRegistry), - std::move(longLivedObjectCollection), - std::move(callback), - std::move(jsQueue), - std::move(nativeQueue), - std::move(devSettings), - std::move(devManager))); - - inner->RegisterForReloadIfNecessary(); - - return inner; - } + TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding), longLivedObjectCollection_); - /*static*/ std::shared_ptr InstanceImpl::MakeAndLoadBundle( - std::shared_ptr&& instance, - std::string&& jsBundleBasePath, - std::string&& jsBundleRelativePath, - std::vector< - std::tuple>> - && cxxModules, - std::shared_ptr turboModuleRegistry, - std::unique_ptr&& callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) noexcept { - auto inner = std::shared_ptr(new InstanceImpl( - std::move(instance), - std::move(jsBundleBasePath), - std::move(cxxModules), - std::move(turboModuleRegistry), - nullptr, - std::move(callback), - std::move(jsQueue), - std::move(nativeQueue), - std::move(devSettings), - std::move(devManager))); - - inner->loadBundle(std::move(jsBundleRelativePath)); - inner->RegisterForReloadIfNecessary(); - - return inner; + // init TurboModule + for (const auto &moduleName : turboModuleManager->getEagerInitModuleNames()) { + turboModuleManager->getModule(moduleName); } - void InstanceImpl::SetInError() noexcept { - m_isInError = true; - } + return std::make_unique( + runtimeHolder_->getRuntime(), + std::move(delegate), + JSIExecutor::defaultTimeoutInvoker, + [isProfiling = isProfilingEnabled_]([[maybe_unused]] jsi::Runtime &runtime) { +#ifdef ENABLE_JS_SYSTRACE_TO_ETW + facebook::react::tracing::initializeJSHooks(runtime, isProfiling); +#endif + }); + } - namespace { - bool shouldStartHermesInspector(DevSettings& devSettings) { - bool isHermes = - ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || - (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && - devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); - - if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) - return true; - else - return false; - } - } // namespace - - InstanceImpl::InstanceImpl( - std::shared_ptr&& instance, - std::string&& jsBundleBasePath, - std::vector< - std::tuple>> - && cxxModules, + OJSIExecutorFactory( + std::shared_ptr runtimeHolder, + NativeLoggingHook loggingHook, std::shared_ptr turboModuleRegistry, - std::shared_ptr longLivedObjectCollection, - std::unique_ptr&& callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager) - : m_turboModuleRegistry(std::move(turboModuleRegistry)), + std::shared_ptr longLivedObjectCollection, + bool isProfilingEnabled, + std::shared_ptr jsCallInvoker) noexcept + : runtimeHolder_{std::move(runtimeHolder)}, + loggingHook_{std::move(loggingHook)}, + turboModuleRegistry_{std::move(turboModuleRegistry)}, + longLivedObjectCollection_{std::move(longLivedObjectCollection)}, + jsCallInvoker_{std::move(jsCallInvoker)}, + isProfilingEnabled_{isProfilingEnabled} {} + + private: + std::shared_ptr runtimeHolder_; + std::shared_ptr turboModuleRegistry_; + std::shared_ptr longLivedObjectCollection_; + std::shared_ptr jsCallInvoker_; + NativeLoggingHook loggingHook_; + bool isProfilingEnabled_; +}; + +} // namespace + +void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const char * /*tag*/) {} + +/*static*/ std::shared_ptr InstanceImpl::MakeNoBundle( + std::shared_ptr &&instance, + std::string &&jsBundleBasePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept { + auto inner = std::shared_ptr(new InstanceImpl( + std::move(instance), + std::move(jsBundleBasePath), + std::move(cxxModules), + std::move(turboModuleRegistry), + std::move(longLivedObjectCollection), + std::move(callback), + std::move(jsQueue), + std::move(nativeQueue), + std::move(devSettings), + std::move(devManager))); + + inner->RegisterForReloadIfNecessary(); + + return inner; +} + +/*static*/ std::shared_ptr InstanceImpl::MakeAndLoadBundle( + std::shared_ptr &&instance, + std::string &&jsBundleBasePath, + std::string &&jsBundleRelativePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) noexcept { + auto inner = std::shared_ptr(new InstanceImpl( + std::move(instance), + std::move(jsBundleBasePath), + std::move(cxxModules), + std::move(turboModuleRegistry), + nullptr, + std::move(callback), + std::move(jsQueue), + std::move(nativeQueue), + std::move(devSettings), + std::move(devManager))); + + inner->loadBundle(std::move(jsBundleRelativePath)); + inner->RegisterForReloadIfNecessary(); + + return inner; +} + +void InstanceImpl::SetInError() noexcept { + m_isInError = true; +} + +namespace { +bool shouldStartHermesInspector(DevSettings &devSettings) { + bool isHermes = + ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || + (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && + devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); + + if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) + return true; + else + return false; +} +} // namespace + +InstanceImpl::InstanceImpl( + std::shared_ptr &&instance, + std::string &&jsBundleBasePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings, + std::shared_ptr devManager) + : m_turboModuleRegistry(std::move(turboModuleRegistry)), m_longLivedObjectCollection(std::move(longLivedObjectCollection)), m_jsThread(std::move(jsQueue)), m_nativeQueue(nativeQueue), @@ -259,439 +257,430 @@ namespace facebook { m_devSettings(std::move(devSettings)), m_devManager(std::move(devManager)), m_innerInstance(std::move(instance)) { - // Temp set the logmarker here - facebook::react::ReactMarker::logTaggedMarker = logMarker; + // Temp set the logmarker here + facebook::react::ReactMarker::logTaggedMarker = logMarker; #ifdef ENABLE_ETW_TRACING - // TODO :: Find a better place to initialize ETW once per process. - facebook::react::tracing::initializeETW(); + // TODO :: Find a better place to initialize ETW once per process. + facebook::react::tracing::initializeETW(); #endif - if (shouldStartHermesInspector(*m_devSettings)) { - m_devManager->StartInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort); - } - - // Default (common) NativeModules - auto modules = GetDefaultNativeModules(nativeQueue); + if (shouldStartHermesInspector(*m_devSettings)) { + m_devManager->StartInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort); + } - // Add app provided modules. - for (auto& cxxModule : cxxModules) { - modules.push_back(std::make_unique( - m_innerInstance, move(std::get<0>(cxxModule)), move(std::get<1>(cxxModule)), move(std::get<2>(cxxModule)))); - } - m_moduleRegistry = std::make_shared(std::move(modules)); - - // Choose JSExecutor - std::shared_ptr jsef; - if (m_devSettings->useWebDebugger) { - try { - auto jseFunc = m_devManager->LoadJavaScriptInProxyMode(*m_devSettings, [weakthis = weak_from_this()]() { - if (auto strongThis = weakthis.lock()) { - strongThis->SetInError(); - } - }); - - if ((jseFunc == nullptr) || m_isInError) { - m_devSettings->errorCallback("Failed to create JavaScript Executor."); - return; - } + // Default (common) NativeModules + auto modules = GetDefaultNativeModules(nativeQueue); - jsef = std::make_shared(std::move(jseFunc)); - } - catch (std::exception& e) { - m_devSettings->errorCallback(e.what()); - return; + // Add app provided modules. + for (auto &cxxModule : cxxModules) { + modules.push_back(std::make_unique( + m_innerInstance, move(std::get<0>(cxxModule)), move(std::get<1>(cxxModule)), move(std::get<2>(cxxModule)))); + } + m_moduleRegistry = std::make_shared(std::move(modules)); + + // Choose JSExecutor + std::shared_ptr jsef; + if (m_devSettings->useWebDebugger) { + try { + auto jseFunc = m_devManager->LoadJavaScriptInProxyMode(*m_devSettings, [weakthis = weak_from_this()]() { + if (auto strongThis = weakthis.lock()) { + strongThis->SetInError(); } + }); + + if ((jseFunc == nullptr) || m_isInError) { + m_devSettings->errorCallback("Failed to create JavaScript Executor."); + return; } - else { - if (m_devSettings->useFastRefresh || m_devSettings->liveReloadCallback) { - Microsoft::ReactNative::PackagerConnection::CreateOrReusePackagerConnection(*m_devSettings); - } - // If the consumer gives us a JSI runtime, then use it. - if (m_devSettings->jsiRuntimeHolder) { - assert(m_devSettings->jsiEngineOverride == JSIEngineOverride::Default); - jsef = std::make_shared( - m_devSettings->jsiRuntimeHolder, - m_devSettings->loggingCallback, - m_turboModuleRegistry, - m_longLivedObjectCollection, - !m_devSettings->useFastRefresh, - m_innerInstance->getJSCallInvoker()); - } - else { - assert(m_devSettings->jsiEngineOverride != JSIEngineOverride::Default); - switch (m_devSettings->jsiEngineOverride) { - case JSIEngineOverride::Hermes: - m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread); - break; - case JSIEngineOverride::V8: { + jsef = std::make_shared(std::move(jseFunc)); + } catch (std::exception &e) { + m_devSettings->errorCallback(e.what()); + return; + } + } else { + if (m_devSettings->useFastRefresh || m_devSettings->liveReloadCallback) { + Microsoft::ReactNative::PackagerConnection::CreateOrReusePackagerConnection(*m_devSettings); + } + + // If the consumer gives us a JSI runtime, then use it. + if (m_devSettings->jsiRuntimeHolder) { + assert(m_devSettings->jsiEngineOverride == JSIEngineOverride::Default); + jsef = std::make_shared( + m_devSettings->jsiRuntimeHolder, + m_devSettings->loggingCallback, + m_turboModuleRegistry, + m_longLivedObjectCollection, + !m_devSettings->useFastRefresh, + m_innerInstance->getJSCallInvoker()); + } else { + assert(m_devSettings->jsiEngineOverride != JSIEngineOverride::Default); + switch (m_devSettings->jsiEngineOverride) { + case JSIEngineOverride::Hermes: + m_devSettings->jsiRuntimeHolder = std::make_shared(m_devSettings, m_jsThread); + break; + case JSIEngineOverride::V8: { #if defined(USE_V8) - std::unique_ptr scriptStore = nullptr; - std::unique_ptr preparedScriptStore = nullptr; + std::unique_ptr scriptStore = nullptr; + std::unique_ptr preparedScriptStore = nullptr; - char tempPath[MAX_PATH]; - if (GetTempPathA(MAX_PATH, tempPath)) { - preparedScriptStore = std::make_unique(tempPath); - } + char tempPath[MAX_PATH]; + if (GetTempPathA(MAX_PATH, tempPath)) { + preparedScriptStore = std::make_unique(tempPath); + } - m_devSettings->jsiRuntimeHolder = std::make_shared( + m_devSettings->jsiRuntimeHolder = std::make_shared( m_devSettings, m_jsThread, std::move(scriptStore), std::move(preparedScriptStore)); - break; + break; #else - assert(false); // V8 is not available in this build, fallthrough - [[fallthrough]]; + assert(false); // V8 is not available in this build, fallthrough + [[fallthrough]]; #endif - } - case JSIEngineOverride::V8NodeApi: { + } + case JSIEngineOverride::V8NodeApi: { #if defined(USE_V8) - std::unique_ptr preparedScriptStore; + std::unique_ptr preparedScriptStore; - wchar_t tempPath[MAX_PATH]; - if (GetTempPathW(static_cast(std::size(tempPath)), tempPath)) { - preparedScriptStore = + wchar_t tempPath[MAX_PATH]; + if (GetTempPathW(static_cast(std::size(tempPath)), tempPath)) { + preparedScriptStore = std::make_unique(winrt::to_string(tempPath)); - } + } - if (!preparedScriptStore) { - if (m_devSettings->errorCallback) - m_devSettings->errorCallback("Could not initialize prepared script store"); + if (!preparedScriptStore) { + if (m_devSettings->errorCallback) + m_devSettings->errorCallback("Could not initialize prepared script store"); - break; - } + break; + } - m_devSettings->jsiRuntimeHolder = make_shared( + m_devSettings->jsiRuntimeHolder = make_shared( m_devSettings, m_jsThread, nullptr /*scriptStore*/, std::move(preparedScriptStore)); - break; + break; #else - if (m_devSettings->errorCallback) - m_devSettings->errorCallback("JSI/V8/NAPI engine is not available in this build"); - assert(false); - [[fallthrough]]; + if (m_devSettings->errorCallback) + m_devSettings->errorCallback("JSI/V8/NAPI engine is not available in this build"); + assert(false); + [[fallthrough]]; #endif - } - case JSIEngineOverride::Chakra: - case JSIEngineOverride::ChakraCore: - default: // TODO: Add other engines once supported - m_devSettings->jsiRuntimeHolder = - std::make_shared(m_devSettings, m_jsThread, nullptr, nullptr); - break; - } - jsef = std::make_shared( - m_devSettings->jsiRuntimeHolder, - m_devSettings->loggingCallback, - m_turboModuleRegistry, - m_longLivedObjectCollection, - !m_devSettings->useFastRefresh, - m_innerInstance->getJSCallInvoker()); } + case JSIEngineOverride::Chakra: + case JSIEngineOverride::ChakraCore: + default: // TODO: Add other engines once supported + m_devSettings->jsiRuntimeHolder = + std::make_shared(m_devSettings, m_jsThread, nullptr, nullptr); + break; } - - m_innerInstance->initializeBridge(std::move(callback), jsef, m_jsThread, m_moduleRegistry); - - // All JSI runtimes do support host objects and hence the native modules - // proxy. - const bool isNativeModulesProxyAvailable = ((m_devSettings->jsiRuntimeHolder != nullptr) || - (m_devSettings->jsiEngineOverride != JSIEngineOverride::Default)) && - !m_devSettings->useWebDebugger; - if (!isNativeModulesProxyAvailable) { - folly::dynamic configArray = folly::dynamic::array; - for (auto const& moduleName : m_moduleRegistry->moduleNames()) { - auto moduleConfig = m_moduleRegistry->getConfig(moduleName); - configArray.push_back(moduleConfig ? std::move(moduleConfig->config) : nullptr); - } - - folly::dynamic configs = folly::dynamic::object("remoteModuleConfig", std::move(configArray)); - m_innerInstance->setGlobalVariable( - "__fbBatchedBridgeConfig", std::make_unique(folly::toJson(configs))); - } - } - - void InstanceImpl::loadBundle(std::string&& jsBundleRelativePath) { - loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ false); + jsef = std::make_shared( + m_devSettings->jsiRuntimeHolder, + m_devSettings->loggingCallback, + m_turboModuleRegistry, + m_longLivedObjectCollection, + !m_devSettings->useFastRefresh, + m_innerInstance->getJSCallInvoker()); } + } - void InstanceImpl::loadBundleSync(std::string&& jsBundleRelativePath) { - loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ true); + m_innerInstance->initializeBridge(std::move(callback), jsef, m_jsThread, m_moduleRegistry); + + // All JSI runtimes do support host objects and hence the native modules + // proxy. + const bool isNativeModulesProxyAvailable = ((m_devSettings->jsiRuntimeHolder != nullptr) || + (m_devSettings->jsiEngineOverride != JSIEngineOverride::Default)) && + !m_devSettings->useWebDebugger; + if (!isNativeModulesProxyAvailable) { + folly::dynamic configArray = folly::dynamic::array; + for (auto const &moduleName : m_moduleRegistry->moduleNames()) { + auto moduleConfig = m_moduleRegistry->getConfig(moduleName); + configArray.push_back(moduleConfig ? std::move(moduleConfig->config) : nullptr); } - // Note: Based on - // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactCommon/cxxreact/Instance.cpp#L112 - bool isHBCBundle(const std::string& bundle) { - static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3; - - // Note:: Directly access the pointer to avoid copy/length-check. It matters as this string contains the bundle which - // can be potentially huge. - // https://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/#comment-483 - auto header = reinterpret_cast(&bundle[0]); - if (HBCBundleMagicNumber == folly::Endian::little(header->magic)) { - return true; - } - else { - return false; - } - } + folly::dynamic configs = folly::dynamic::object("remoteModuleConfig", std::move(configArray)); + m_innerInstance->setGlobalVariable( + "__fbBatchedBridgeConfig", std::make_unique(folly::toJson(configs))); + } +} + +void InstanceImpl::loadBundle(std::string &&jsBundleRelativePath) { + loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ false); +} + +void InstanceImpl::loadBundleSync(std::string &&jsBundleRelativePath) { + loadBundleInternal(std::move(jsBundleRelativePath), /*synchronously:*/ true); +} + +// Note: Based on +// https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactCommon/cxxreact/Instance.cpp#L112 +bool isHBCBundle(const std::string &bundle) { + static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3; + + // Note:: Directly access the pointer to avoid copy/length-check. It matters as this string contains the bundle which + // can be potentially huge. + // https://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/#comment-483 + auto header = reinterpret_cast(&bundle[0]); + if (HBCBundleMagicNumber == folly::Endian::little(header->magic)) { + return true; + } else { + return false; + } +} - void InstanceImpl::loadBundleInternal(std::string&& jsBundleRelativePath, bool synchronously) { - try { - if (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback != nullptr || - m_devSettings->useFastRefresh) { - // First attempt to get download the Js locally, to catch any bundling - // errors before attempting to load the actual script. +void InstanceImpl::loadBundleInternal(std::string &&jsBundleRelativePath, bool synchronously) { + try { + if (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback != nullptr || + m_devSettings->useFastRefresh) { + // First attempt to get download the Js locally, to catch any bundling + // errors before attempting to load the actual script. - uint32_t hermesBytecodeVersion = 0; + uint32_t hermesBytecodeVersion = 0; #if defined(USE_HERMES) && defined(ENABLE_DEVSERVER_HBCBUNDLES) - hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; + hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; #endif - auto [jsBundleString, success] = Microsoft::ReactNative::GetJavaScriptFromServer( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, - m_devSettings->platformName, - true /* dev */, - m_devSettings->useFastRefresh, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion); + auto [jsBundleString, success] = Microsoft::ReactNative::GetJavaScriptFromServer( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, + m_devSettings->platformName, + true /* dev */, + m_devSettings->useFastRefresh, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion); - if (!success) { - m_devManager->UpdateBundleStatus(false, -1); - m_devSettings->errorCallback(jsBundleString); - return; - } + if (!success) { + m_devManager->UpdateBundleStatus(false, -1); + m_devSettings->errorCallback(jsBundleString); + return; + } - int64_t currentTimeInMilliSeconds = - std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) - .count(); - m_devManager->UpdateBundleStatus(true, currentTimeInMilliSeconds); + int64_t currentTimeInMilliSeconds = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); + m_devManager->UpdateBundleStatus(true, currentTimeInMilliSeconds); - auto bundleUrl = DevServerHelper::get_BundleUrl( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, - m_devSettings->platformName, - /*dev*/ true, - /*hot*/ false, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion); - - // This code is based on the HBC Bundle integration on Android - // Ref: - // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp#L231 - if (isHBCBundle(jsBundleString)) { - auto script = std::make_unique(jsBundleString, false); - const char* buffer = script->c_str(); - uint32_t bufferLength = (uint32_t)script->size(); - - // Please refer the code here for details on the file format: - // https://github.com/facebook/metro/blob/b1bacf52070be62872d6bd3420f37a4405ed34e6/packages/metro/src/lib/bundleToBytecode.js#L29 - // Essentially, there is an 8 byte long file header with 4 bytes of a magic number followed by 4 bytes to encode - // the number of modules.The module buffers follows, each one starts with 4 byte header which encodes module - // length.A properly formatted HBCB should have at least 8 bytes.. - uint32_t offset = 8; + auto bundleUrl = DevServerHelper::get_BundleUrl( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath.empty() ? jsBundleRelativePath : m_devSettings->debugBundlePath, + m_devSettings->platformName, + /*dev*/ true, + /*hot*/ false, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion); + + // This code is based on the HBC Bundle integration on Android + // Ref: + // https://github.com/facebook/react-native/blob/24d91268b64c7abbd4b26547ffcc663dc90ec5e7/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp#L231 + if (isHBCBundle(jsBundleString)) { + auto script = std::make_unique(jsBundleString, false); + const char *buffer = script->c_str(); + uint32_t bufferLength = (uint32_t)script->size(); + + // Please refer the code here for details on the file format: + // https://github.com/facebook/metro/blob/b1bacf52070be62872d6bd3420f37a4405ed34e6/packages/metro/src/lib/bundleToBytecode.js#L29 + // Essentially, there is an 8 byte long file header with 4 bytes of a magic number followed by 4 bytes to encode + // the number of modules.The module buffers follows, each one starts with 4 byte header which encodes module + // length.A properly formatted HBCB should have at least 8 bytes.. + uint32_t offset = 8; #define __SAFEADD__(s1, s2, t) \ if (!msl::utilities::SafeAdd(s1, s2, t)) \ break; - while (offset < bufferLength) { - uint32_t segment; - __SAFEADD__(offset, 4, segment) - uint32_t moduleLength = (bufferLength < segment) ? 0 : *(((uint32_t*)buffer) + offset / 4); - - // Early break if the module length is computed as 0.. as the segment start may be overflowing the buffer. - if (moduleLength == 0) - break; - - uint32_t segmentEnd; - __SAFEADD__(moduleLength, segment, segmentEnd) - // Early break if the segment overflows beyond the buffer. This is unlikely for a properly formatted - // HBCB though. - if (segmentEnd > bufferLength) - break; - - m_innerInstance->loadScriptFromString( - std::make_unique(std::string(buffer + segment, buffer + segmentEnd)), - bundleUrl, - false); - - // Aligned at 4 byte boundary. - offset += ((moduleLength + 3) & ~3) + 4; - } -#undef __SAFEADD__ - } - else { - // Remote debug executor loads script from a Uri, rather than taking the actual bundle string - m_innerInstance->loadScriptFromString( - std::make_unique(m_devSettings->useWebDebugger ? bundleUrl : jsBundleString), + while (offset < bufferLength) { + uint32_t segment; + __SAFEADD__(offset, 4, segment) + uint32_t moduleLength = (bufferLength < segment) ? 0 : *(((uint32_t *)buffer) + offset / 4); + + // Early break if the module length is computed as 0.. as the segment start may be overflowing the buffer. + if (moduleLength == 0) + break; + + uint32_t segmentEnd; + __SAFEADD__(moduleLength, segment, segmentEnd) + // Early break if the segment overflows beyond the buffer. This is unlikely for a properly formatted + // HBCB though. + if (segmentEnd > bufferLength) + break; + + m_innerInstance->loadScriptFromString( + std::make_unique(std::string(buffer + segment, buffer + segmentEnd)), bundleUrl, - synchronously); - } - } - else { -#if (defined(_MSC_VER) && !defined(WINRT)) - std::string bundlePath = (fs::path(m_devSettings->bundleRootPath) / jsBundleRelativePath).string(); - auto bundleString = FileMappingBigString::fromPath(bundlePath); -#else - std::string bundlePath; - if (m_devSettings->bundleRootPath._Starts_with("resource://")) { - auto uri = winrt::Windows::Foundation::Uri( - winrt::to_hstring(m_devSettings->bundleRootPath), winrt::to_hstring(jsBundleRelativePath)); - bundlePath = winrt::to_string(uri.ToString()); - } - else { - bundlePath = (fs::path(m_devSettings->bundleRootPath) / (jsBundleRelativePath + ".bundle")).string(); - } + false); - auto bundleString = std::make_unique<::Microsoft::ReactNative::StorageFileBigString>(bundlePath); -#endif - m_innerInstance->loadScriptFromString(std::move(bundleString), std::move(jsBundleRelativePath), synchronously); + // Aligned at 4 byte boundary. + offset += ((moduleLength + 3) & ~3) + 4; } +#undef __SAFEADD__ + } else { + // Remote debug executor loads script from a Uri, rather than taking the actual bundle string + m_innerInstance->loadScriptFromString( + std::make_unique(m_devSettings->useWebDebugger ? bundleUrl : jsBundleString), + bundleUrl, + synchronously); } - catch (const std::exception& e) { - m_devSettings->errorCallback(e.what()); + } else { +#if (defined(_MSC_VER) && !defined(WINRT)) + std::string bundlePath = (fs::path(m_devSettings->bundleRootPath) / jsBundleRelativePath).string(); + auto bundleString = FileMappingBigString::fromPath(bundlePath); +#else + std::string bundlePath; + if (m_devSettings->bundleRootPath._Starts_with("resource://")) { + auto uri = winrt::Windows::Foundation::Uri( + winrt::to_hstring(m_devSettings->bundleRootPath), winrt::to_hstring(jsBundleRelativePath)); + bundlePath = winrt::to_string(uri.ToString()); + } else { + bundlePath = (fs::path(m_devSettings->bundleRootPath) / (jsBundleRelativePath + ".bundle")).string(); } - catch (const winrt::hresult_error& hrerr) { - auto error = fmt::format("[0x{:0>8x}] {}", static_cast(hrerr.code()), winrt::to_string(hrerr.message())); - m_devSettings->errorCallback(std::move(error)); - } + auto bundleString = std::make_unique<::Microsoft::ReactNative::StorageFileBigString>(bundlePath); +#endif + m_innerInstance->loadScriptFromString(std::move(bundleString), std::move(jsBundleRelativePath), synchronously); } + } catch (const std::exception &e) { + m_devSettings->errorCallback(e.what()); + } catch (const winrt::hresult_error &hrerr) { + auto error = fmt::format("[0x{:0>8x}] {}", static_cast(hrerr.code()), winrt::to_string(hrerr.message())); - InstanceImpl::~InstanceImpl() { - if (m_devSettings->jsiEngineOverride == JSIEngineOverride::Hermes) { - m_devManager->StopInspector(); - } - m_nativeQueue->quitSynchronous(); - } + m_devSettings->errorCallback(std::move(error)); + } +} + +InstanceImpl::~InstanceImpl() { + if (m_devSettings->jsiEngineOverride == JSIEngineOverride::Hermes) { + m_devManager->StopInspector(); + } + m_nativeQueue->quitSynchronous(); +} - std::vector> InstanceImpl::GetDefaultNativeModules( - std::shared_ptr nativeQueue) { - std::vector> modules; - auto transitionalProps{ ReactPropertyBagHelper::CreatePropertyBag() }; +std::vector> InstanceImpl::GetDefaultNativeModules( + std::shared_ptr nativeQueue) { + std::vector> modules; + auto transitionalProps{ReactPropertyBagHelper::CreatePropertyBag()}; #if (defined(_MSC_VER) && !defined(WINRT)) - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetHttpModuleName(), - [nativeQueue, transitionalProps]() -> std::unique_ptr { - return Microsoft::React::CreateHttpModule(transitionalProps); - }, - nativeQueue)); + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetHttpModuleName(), + [nativeQueue, transitionalProps]() -> std::unique_ptr { + return Microsoft::React::CreateHttpModule(transitionalProps); + }, + nativeQueue)); #endif - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetWebSocketModuleName(), - [nativeQueue, transitionalProps]() -> std::unique_ptr { - return Microsoft::React::CreateWebSocketModule(transitionalProps); - }, - nativeQueue)); - - // TODO: This is not included for UWP because we have a different module which - // is added later. However, this one is designed - // so that we can base a UWP version on it. We need to do that but is not high - // priority. + modules.push_back(std::make_unique( + m_innerInstance, + Microsoft::React::GetWebSocketModuleName(), + [nativeQueue, transitionalProps]() -> std::unique_ptr { + return Microsoft::React::CreateWebSocketModule(transitionalProps); + }, + nativeQueue)); + + // TODO: This is not included for UWP because we have a different module which + // is added later. However, this one is designed + // so that we can base a UWP version on it. We need to do that but is not high + // priority. #if (defined(_MSC_VER) && !defined(WINRT)) - modules.push_back(std::make_unique( - m_innerInstance, - "Timing", - [nativeQueue]() -> std::unique_ptr { return react::CreateTimingModule(nativeQueue); }, - nativeQueue)); + modules.push_back(std::make_unique( + m_innerInstance, + "Timing", + [nativeQueue]() -> std::unique_ptr { return react::CreateTimingModule(nativeQueue); }, + nativeQueue)); #endif - uint32_t hermesBytecodeVersion = 0; + uint32_t hermesBytecodeVersion = 0; #if defined(USE_HERMES) && defined(ENABLE_DEVSERVER_HBCBUNDLES) - hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; + hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; #endif - // TODO - Encapsulate this in a helpers, and make sure callers add it to their - // list - std::string bundleUrl = (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback) - ? DevServerHelper::get_BundleUrl( - m_devSettings->sourceBundleHost, - m_devSettings->sourceBundlePort, - m_devSettings->debugBundlePath, - m_devSettings->platformName, - true /*dev*/, - m_devSettings->useFastRefresh, - m_devSettings->inlineSourceMap, - hermesBytecodeVersion) - : std::string(); - modules.push_back(std::make_unique( - m_innerInstance, - facebook::react::SourceCodeModule::Name, - [bundleUrl]() -> std::unique_ptr { - return std::make_unique(bundleUrl); - }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - "ExceptionsManager", - [redboxHandler = m_devSettings->redboxHandler]() mutable { - return std::make_unique(redboxHandler); - }, - nativeQueue)); - - modules.push_back(std::make_unique( + // TODO - Encapsulate this in a helpers, and make sure callers add it to their + // list + std::string bundleUrl = (m_devSettings->useWebDebugger || m_devSettings->liveReloadCallback) + ? DevServerHelper::get_BundleUrl( + m_devSettings->sourceBundleHost, + m_devSettings->sourceBundlePort, + m_devSettings->debugBundlePath, + m_devSettings->platformName, + true /*dev*/, + m_devSettings->useFastRefresh, + m_devSettings->inlineSourceMap, + hermesBytecodeVersion) + : std::string(); + modules.push_back(std::make_unique( + m_innerInstance, + facebook::react::SourceCodeModule::Name, + [bundleUrl]() -> std::unique_ptr { + return std::make_unique(bundleUrl); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + "ExceptionsManager", + [redboxHandler = m_devSettings->redboxHandler]() mutable { + return std::make_unique(redboxHandler); + }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + PlatformConstantsModule::Name, + []() { return std::make_unique(); }, + nativeQueue)); + + modules.push_back(std::make_unique( + m_innerInstance, + StatusBarManagerModule::Name, + []() { return std::make_unique(); }, + nativeQueue)); + + // These modules are instantiated separately in MSRN (Universal Windows). + // When there are module name colisions, the last one registered is used. + // If this code is enabled, we will have unused module instances. + // Also, MSRN has a different property bag mechanism incompatible with this method's transitionalProps variable. +#if (defined(_MSC_VER) && !defined(WINRT)) + if (Microsoft::React::GetRuntimeOptionBool("Blob.EnableModule") && + !Microsoft::React::GetRuntimeOptionBool("Http.UseMonolithicModule")) { + modules.push_back(std::make_unique( m_innerInstance, - PlatformConstantsModule::Name, - []() { return std::make_unique(); }, + Microsoft::React::GetBlobModuleName(), + [transitionalProps]() { return Microsoft::React::CreateBlobModule(transitionalProps); }, nativeQueue)); - modules.push_back(std::make_unique( + modules.push_back(std::make_unique( m_innerInstance, - StatusBarManagerModule::Name, - []() { return std::make_unique(); }, + Microsoft::React::GetFileReaderModuleName(), + [transitionalProps]() { return Microsoft::React::CreateFileReaderModule(transitionalProps); }, nativeQueue)); - - // These modules are instantiated separately in MSRN (Universal Windows). - // When there are module name colisions, the last one registered is used. - // If this code is enabled, we will have unused module instances. - // Also, MSRN has a different property bag mechanism incompatible with this method's transitionalProps variable. -#if (defined(_MSC_VER) && !defined(WINRT)) - if (Microsoft::React::GetRuntimeOptionBool("Blob.EnableModule") && - !Microsoft::React::GetRuntimeOptionBool("Http.UseMonolithicModule")) { - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetBlobModuleName(), - [transitionalProps]() { return Microsoft::React::CreateBlobModule(transitionalProps); }, - nativeQueue)); - - modules.push_back(std::make_unique( - m_innerInstance, - Microsoft::React::GetFileReaderModuleName(), - [transitionalProps]() { return Microsoft::React::CreateFileReaderModule(transitionalProps); }, - nativeQueue)); - } + } #endif - return modules; - } + return modules; +} - void InstanceImpl::RegisterForReloadIfNecessary() noexcept { - // setup polling for live reload - if (!m_isInError && !m_devSettings->useFastRefresh && m_devSettings->liveReloadCallback != nullptr) { - m_devManager->StartPollingLiveReload( - m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort, m_devSettings->liveReloadCallback); - } - } +void InstanceImpl::RegisterForReloadIfNecessary() noexcept { + // setup polling for live reload + if (!m_isInError && !m_devSettings->useFastRefresh && m_devSettings->liveReloadCallback != nullptr) { + m_devManager->StartPollingLiveReload( + m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort, m_devSettings->liveReloadCallback); + } +} - void InstanceImpl::DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic&& eventData) { - if (m_isInError) { - return; - } +void InstanceImpl::DispatchEvent(int64_t viewTag, std::string eventName, folly::dynamic &&eventData) { + if (m_isInError) { + return; + } - folly::dynamic params = folly::dynamic::array(viewTag, eventName, std::move(eventData)); - m_innerInstance->callJSFunction("RCTEventEmitter", "receiveEvent", std::move(params)); - } + folly::dynamic params = folly::dynamic::array(viewTag, eventName, std::move(eventData)); + m_innerInstance->callJSFunction("RCTEventEmitter", "receiveEvent", std::move(params)); +} - void InstanceImpl::invokeCallback(const int64_t callbackId, folly::dynamic&& params) { - if (m_isInError) { - return; - } +void InstanceImpl::invokeCallback(const int64_t callbackId, folly::dynamic &¶ms) { + if (m_isInError) { + return; + } - m_innerInstance->callJSCallback(callbackId, std::move(params)); - } + m_innerInstance->callJSCallback(callbackId, std::move(params)); +} - } // namespace react - } // namespace facebook +} // namespace react +} // namespace facebook From 1f694b5535c1ccfe6d51be47d022d7e120f80afa Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 16 Sep 2022 18:27:23 -0700 Subject: [PATCH 23/23] Revert unwanted changes --- vnext/Shared/OInstance.h | 1 + vnext/Shared/RuntimeOptions.cpp | 9 +- vnext/Shared/RuntimeOptions.h | 17 +- vnext/src/IntegrationTests/BlobTest.js | 237 +++++++++--------- .../websocket_integration_test_server_blob.js | 1 - 5 files changed, 140 insertions(+), 125 deletions(-) diff --git a/vnext/Shared/OInstance.h b/vnext/Shared/OInstance.h index ff4e013ef3a..d09e2d25353 100644 --- a/vnext/Shared/OInstance.h +++ b/vnext/Shared/OInstance.h @@ -10,6 +10,7 @@ #include "InstanceManager.h" // React Native +#include #include // Standard Libriary diff --git a/vnext/Shared/RuntimeOptions.cpp b/vnext/Shared/RuntimeOptions.cpp index f581ffe2309..c149db25b4b 100644 --- a/vnext/Shared/RuntimeOptions.cpp +++ b/vnext/Shared/RuntimeOptions.cpp @@ -94,7 +94,7 @@ void __cdecl MicrosoftReactSetRuntimeOptionString(const char *name, const char * g_runtimeOptionStrings.erase(name); } -const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept { +bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept { scoped_lock lock{g_runtimeOptionsMutex}; auto itr = g_runtimeOptionInts.find(name); if (itr != g_runtimeOptionInts.end()) @@ -103,7 +103,7 @@ const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept return false; } -const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexcept { +int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexcept { scoped_lock lock{g_runtimeOptionsMutex}; auto itr = g_runtimeOptionInts.find(name); if (itr != g_runtimeOptionInts.end()) @@ -112,7 +112,10 @@ const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexce return 0; } -void MicrosoftReactGetRuntimeOptionString(const char *name, MicrosoftReactGetStringCallback callBack, void *state) { +void __cdecl MicrosoftReactGetRuntimeOptionString( + const char *name, + MicrosoftReactGetStringCallback callBack, + void *state) { scoped_lock lock{g_runtimeOptionsMutex}; auto itr = g_runtimeOptionStrings.find(name); if (itr != g_runtimeOptionStrings.cend()) { diff --git a/vnext/Shared/RuntimeOptions.h b/vnext/Shared/RuntimeOptions.h index 80cc9111dd8..8860d4a9c76 100644 --- a/vnext/Shared/RuntimeOptions.h +++ b/vnext/Shared/RuntimeOptions.h @@ -3,6 +3,10 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + /// /// Sets a global boolean value identified by an arbitrary string. /// @@ -26,14 +30,14 @@ void __cdecl MicrosoftReactSetRuntimeOptionString(const char *name, const char * /// /// Global boolean key /// Value stored for the given key, or false if the entry doesn't exist (default) -const bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept; +bool __cdecl MicrosoftReactGetRuntimeOptionBool(const char *name) noexcept; /// /// Retrieves a global boolean value for the given key. /// /// Global key /// Value stored for the given key, or 0 if the entry doesn't exist (default) -const int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexcept; +int32_t __cdecl MicrosoftReactGetRuntimeOptionInt(const char *name) noexcept; /// String contents. nullptr if none found /// String length. 0 if none found @@ -46,4 +50,11 @@ typedef void(__cdecl *MicrosoftReactGetStringCallback)(const char *buffer, size_ /// Global key /// Handler used to access the obtained string /// Arbitrary data to pass on to or retrieve from callBack -void MicrosoftReactGetRuntimeOptionString(const char *name, MicrosoftReactGetStringCallback callBack, void *state); +void __cdecl MicrosoftReactGetRuntimeOptionString( + const char *name, + MicrosoftReactGetStringCallback callBack, + void *state); + +#ifdef __cplusplus +} +#endif diff --git a/vnext/src/IntegrationTests/BlobTest.js b/vnext/src/IntegrationTests/BlobTest.js index c104386ab2d..415aa95b2ea 100644 --- a/vnext/src/IntegrationTests/BlobTest.js +++ b/vnext/src/IntegrationTests/BlobTest.js @@ -11,12 +11,12 @@ const ReactNative = require('react-native'); const {AppRegistry, View} = ReactNative; -const {TestModule} = ReactNative.NativeModules; +const {TestModule, FileReader} = ReactNative.NativeModules; type State = { statusCode: number, xhr: XMLHttpRequest, - expected: string, + expected: String, }; class BlobTest extends React.Component<{...}, State> { @@ -24,122 +24,123 @@ class BlobTest extends React.Component<{...}, State> { statusCode: 0, xhr: new XMLHttpRequest(), // https://www.facebook.com/favicon.ico - expected: + expected: new String( 'data:application/octet-stream;base64,' + - 'AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAA' + - 'AAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOFl' + - 'BiviZgKP4WYB1f//////////4WUA1eJmAI/hawYrAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAA/4ArBuNpA5TkawP942kC/+NpAv///////////+NpAv/jaQL/5GoD/eNp' + - 'A5T/gCsGAAAAAAAAAAAAAAAA/4ArBuVvBL3lbgT/5W4E/+VuBP/lbgT/////////' + - '///lbgT/5W4E/+VuBP/lbgT/5W8Evf+AKwYAAAAAAAAAAOlzBZTncwX/53MF/+dz' + - 'Bf/ncwX/53MF////////////53MF/+dzBf/ncwb/53MF/+dzBv/pcweUAAAAAO19' + - 'DCvpeAf96HcH/+l4B//odwf/6XgH/+l4B////////////+h3B//odwf/6XgH/+h3' + - 'B//peAf/6nkH/e19DCvrfQmP630J/+t9Cf/rfAn/630J/+t8Cf/rfAn/////////' + - '///rfQn/630J/+p8CP/rfQn/6nwI/+p8CP/rfQuP7YEL1e2BCv/tgQr/7IEK/+2B' + - 'Cv/////////////////////////////////uiRj/7IEK/+2CCv/tggr/7YIM1e6G' + - 'DfPvhgz/74YM/++HDP/vhgz/////////////////////////////////8Zw4/++G' + - 'DP/uhgz/7oYM/+6GDPPwiw7z8IsN//CLDf/wiw3/8IsN//CLDf/wiw3/////////' + - '///wig3/8IoN//CLDf/wig3/8IsN//CLDf/xjA/z85EQ1fOQD//zkA//85AP//OQ' + - 'D//zkA//85AP////////////8o8P//KPD//ykA//8o8P//KQD//ykA//85EQ1fSU' + - 'EI/1lRH/9ZUR//SUEP/1lRH/9JQQ//SUEP/+9uz///////jDev/0mRz/9ZUR//SV' + - 'Ef/1lRH/9ZUR//SUEI/5mhgr95kS/faZEv/2mRL/9pkS//aZEv/2mRL//Nqo////' + - '//////////////rLhv/3mhL/9pkS//eZEv35mhgrAAAAAPifFZT4nhT/+Z8U//ie' + - 'FP/5nxT/+Z8U//ikI//83a3//vjw//78+f/7yX3/+J4T//ieFP/4nxWUAAAAAAAA' + - 'AAD/qisG+6MWvfqjFf/6oxX/+qMV//qjFf/6oxX/+qMV//qjFf/6oxX/+qIV//qj' + - 'Ff/7oxa9/6orBgAAAAAAAAAAAAAAAP+qKwb9qRiU/agW/fyoF//8qBf//agX//yo' + - 'F//9qBf//agX//2oF/39qRiU/6orBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+y' + - 'Hiv/rRmP/6wZ1f+tGPP/rBjz/64Z1f+vGY//sh4rAAAAAAAAAAAAAAAAAAAAAPAP' + - 'AADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB' + - 'AACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+A' + - 'AAbiZQRH4GMAlf//////////////////////////4GQAv+BjAJXiZQBH/4AABgAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOpqCxjiZgKW4WYB8eJmAf/hZQH/////////' + - '///////////////////hZgH/4mYB/+FmAf/iZwHx4mYClupqCxgAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9t' + - 'JAfkagSC42kC9ONoAv/jaAL/4mgC/+NoAv///////////////////////////+Jo' + - 'Af/iaAL/4mgB/+JoAv/iaAL/42kC9ORqBIL/bSQHAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADqagsY5GoEx+NqA//jagP/42oD/+Nq' + - 'A//kawP/42oD////////////////////////////42oD/+RrA//jagP/5GsD/+Rr' + - 'A//kawP/5GsD/+RsBMfqdQsYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAA6HEGLeVuBOPlbQT/5GwD/+VtBP/kbAP/5GwD/+VtBP/kbAP/////////' + - '///////////////////kbAP/5G0D/+RsA//kbQP/5G0D/+RtA//kbQP/5G0D/+Ru' + - 'A+PocQYtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOp1CxjmcAbj5W8E/+Vv' + - 'BP/lbwT/5W8E/+VvBP/lbwT/5W8E/+VvBP///////////////////////////+Vv' + - 'BP/mcAX/5W8E/+ZwBf/mcAX/5W8E/+ZwBf/lbwT/5W8E/+ZwBuPqdQsYAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAD/kiQH53IFx+ZyBf/mcQX/5nEF/+ZxBf/mcQX/5nEF/+Zx' + - 'Bf/ncgX/5nEF////////////////////////////53IG/+ZxBf/ncgb/5nEF/+Zx' + - 'Bf/mcgX/5nEF/+ZyBf/mcgX/5nEF/+dyBcf/kiQHAAAAAAAAAAAAAAAAAAAAAOd2' + - 'CILodAb/53QG/+h0Bv/odAb/6HQG/+h0Bv/odAb/6HQG/+d0Bv/odAb/////////' + - '///////////////////ndAX/53QG/+d0Bf/ndAb/53QG/+h1Bv/ndAb/6HUG/+h1' + - 'Bv/ndAX/6HUG/+d2BoIAAAAAAAAAAAAAAADqgAsY6HYG9Oh2B//odgb/6HYH/+h2' + - 'B//odgb/6HYH/+h2Bv/odgb/6HYH/+h2Bv///////////////////////////+l3' + - 'B//odgb/6XcH/+h2Bv/odgb/6HYH/+h2Bv/odgf/6HYH/+h2Bv/odgf/6HYG9OqA' + - 'CxgAAAAAAAAAAOt6CZbpeQj/6nkI/+l5CP/qeQj/6nkI/+l5B//qeQj/6XkH/+l5' + - 'B//peQf/6XkH////////////////////////////6XkI/+l5B//peQj/6XkH/+l5' + - 'B//peQj/6XkH/+l5CP/peQj/6XkH/+l5CP/peQf/63oJlgAAAAD/gCsG7H0K8et8' + - 'Cf/qewj/63wJ/+p7CP/qewj/6nsI/+p7CP/qewj/6nsI/+t8Cf/qewj/////////' + - '///////////////////qewj/6nwJ/+p7CP/qfAn/6nwJ/+p7CP/qfAn/6nsI/+p7' + - 'CP/rfAn/6nsI/+t8Cf/sfQrx/4ArBu2BC0frfQn/630J/+t+Cf/rfQn/634J/+t+' + - 'Cf/rfgn/634J////////////////////////////////////////////////////' + - '///////////////////zs27/634J/+x+Cf/rfgn/634J/+t+Cf/rfgn/634J/+t+' + - 'Cf/tgQtH7IAKleyACv/sgAr/7IAK/+yACv/sgAr/7IAK/+yACv/sgAr/////////' + - '//////////////////////////////////////////////////////////////XC' + - 'iv/sgAr/7IAK/+yACv/sgAr/7IAJ/+yACv/sgAn/7IAJ/+yACpXugwu/7YML/+2D' + - 'C//tggr/7YML/+2CCv/tggr/7YIK/+2CCv//////////////////////////////' + - '////////////////////////////////////////+NKn/+2DC//tggr/7YML/+2D' + - 'C//tgwv/7YML/+2DC//tgwv/7oMLv++GDNnuhQv/7oUL/+6FC//uhQv/7oUL/+6F' + - 'C//vhQz/7oUL////////////////////////////////////////////////////' + - '///////////////////64cT/7oUL/+6FC//uhQv/7oUL/+6EC//uhQv/7oQL/+6E' + - 'C//vhgzZ74gO8++IDP/viAz/74cM/++IDP/vhwz/74cM/++HDP/vhwz/////////' + - '//////////////////////////////////////////////////////////////3w' + - '4f/viA3/74cM/++IDf/viA3/74cM/++IDf/vhwz/74cM/++HDfPwiw7z8IoN//CK' + - 'Df/wig3/8IoN//CKDf/wig3/8IkN//CKDf/wiQ3/8IkN//CKDf/wiQ3/////////' + - '///////////////////wiQ3/8IoN//CJDf/wig3/8IoN//CJDf/wig3/8IkN//CJ' + - 'Df/wiQ3/8IkN//CJDf/wiQ3/8IsO8/KNDtnxjA7/8YwO//GMDf/xjA7/8YwN//GM' + - 'Df/xjA3/8YwN//GMDf/xjA3/8YwO//GMDf////////////////////////////GM' + - 'Dv/xjA7/8YwO//GMDv/xjA7/8YwO//GMDv/xjA7/8YwO//GMDv/xjA7/8YwO//GM' + - 'Dv/yjQ7Z8o8Pv/KPD//yjw//8o8P//KPD//yjw//8o8P//KPD//yjw//8o8P//KP' + - 'D//yjg7/8o8P////////////////////////////8Y4O//KODv/xjg7/8o4O//KO' + - 'Dv/yjg7/8o4O//KODv/yjg7/8o8P//KODv/yjw//8o8P//OQEL/zkQ+V85EP//OR' + - 'D//zkQ//85EP//ORD//zkQ//85EP//ORD//zkQ//85EP//OREP/zkQ///vr0////' + - '///////////////////0myb/85EQ//ORD//zkRD/85EQ//ORD//zkRD/85EP//OR' + - 'D//zkQ//85EP//ORD//zkQ//85EPlfSXEkf0kxD/9JMQ//SUEP/0kxD/9JQQ//SU' + - 'EP/zkxD/9JQQ//OTEP/zkxD/9JQQ//OTEP/86tD///////////////////////rV' + - 'ov/1nSb/85MQ//STEP/0kxD/9JMQ//STEP/0kxD/9JMQ//SUEP/0kxD/9JQQ//SU' + - 'EP/0kxJH/6orBvWWEvH1lhH/9ZYR//WWEf/1lhH/9ZYR//WWEf/1lhH/9ZYR//WW' + - 'Ef/1lhH/9ZYR//vZq///////////////////////////////////////////////' + - '///1lhH/9ZYR//WWEf/1lhH/9ZYR//WWEf/1lhH/9ZYS8f+qKwYAAAAA95kTlvaY' + - 'Ev/2mBH/9pgS//aYEf/2mBH/9ZgR//aYEf/1mBH/9ZgR//aYEv/1mBH/+LFN////' + - '//////////////////////////////////////////////aYEv/1mBH/9pgS//aY' + - 'Ev/1mBH/9pgS//WYEf/3mRGWAAAAAAAAAAD/nxUY+JwU9PebE//3mxP/95sT//eb' + - 'E//3mxP/95sT//ebE//3mxP/95oS//ebE//3mhL//OK7////////////////////' + - '////////////////////////95sT//ebE//3mxP/95sT//aaEv/3mxP/95sT9P+f' + - 'FRgAAAAAAAAAAAAAAAD5nxSC+J0T//idE//4nRP/+J0T//ecE//4nRP/95wT//ec' + - 'E//4nRP/95wT//idE//4pSf//efF////////////////////////////////////' + - '///4nRP/950T//idE//4nRP/+J0T//idE//5nxSCAAAAAAAAAAAAAAAAAAAAAP+2' + - 'JAf6oBXH+aAU//mgFP/5oBT/+J8U//mgFP/4nxT/+J8U//mfFP/4nxT/+Z8U//mf' + - 'FP/5oRf/+86H//7w2v/+/Pj//v36//758f/+8+P//evQ//mgFP/5nxT/+aAU//mg' + - 'FP/4nxT/+qAVx/+2JAcAAAAAAAAAAAAAAAAAAAAAAAAAAP+qFRj7oxXj+aEU//mh' + - 'FP/6ohX/+aEU//qiFf/6ohX/+qIV//qiFf/6ohX/+qIV//mhFP/6ohX/+aEU//mh' + - 'FP/6ohX/+aEU//qiFf/6ohX/+aEU//qiFf/5oRT/+aEU//ujFeP/qhUYAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+mFi78pBXj+6QV//ukFv/7pBX/+6QW//uk' + - 'Fv/6pBX/+6QW//qkFf/6pBX/+6QW//qkFf/7pBb/+6QW//ulFv/7pBb/+6UW//ul' + - 'Fv/7pBX/+6UW//ukFf/8pBXj/6QXLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAP+qIBj8qBfH/KcW//ynF//8pxb//KcW//ynFv/8pxb//KcW//yn' + - 'Fv/7phb//KcW//umFv/7phb/+6YW//umFv/7phb/+6YW//ynFv/7phb//KgXx/+q' + - 'IBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+2' + - 'JAf9qxiC/akY9PypF//8qRf//KgX//ypF//8qBf//KgX//2pF//8qBf//akX//2p' + - 'F//9qRf//akX//2pF//9qRf//qkY9P2rGIL/tiQHAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/tSAY/60alv+s' + - 'GPH+rBj//qwY//6sGP/+rBj//asY//6sGP/9qxj//asY//2rF//9qxj//qsX8f2s' + - 'GJb/tSAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9UrBv+wGUf/rxqV/68Zv/+v' + - 'Gtn/rhnz/64Z8/+vGtn/rxm//68alf+wGUf/1SsGAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/gAAB/wAAAP4AAAB8AA' + - 'AAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAACAAAABgAAAAcAAAAPAAAAD4AAAB/AAAA/4AAAf/AAAP/8A' + - 'AP//wAP/', + 'AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAA' + + 'AAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOFl' + + 'BiviZgKP4WYB1f//////////4WUA1eJmAI/hawYrAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAA/4ArBuNpA5TkawP942kC/+NpAv///////////+NpAv/jaQL/5GoD/eNp' + + 'A5T/gCsGAAAAAAAAAAAAAAAA/4ArBuVvBL3lbgT/5W4E/+VuBP/lbgT/////////' + + '///lbgT/5W4E/+VuBP/lbgT/5W8Evf+AKwYAAAAAAAAAAOlzBZTncwX/53MF/+dz' + + 'Bf/ncwX/53MF////////////53MF/+dzBf/ncwb/53MF/+dzBv/pcweUAAAAAO19' + + 'DCvpeAf96HcH/+l4B//odwf/6XgH/+l4B////////////+h3B//odwf/6XgH/+h3' + + 'B//peAf/6nkH/e19DCvrfQmP630J/+t9Cf/rfAn/630J/+t8Cf/rfAn/////////' + + '///rfQn/630J/+p8CP/rfQn/6nwI/+p8CP/rfQuP7YEL1e2BCv/tgQr/7IEK/+2B' + + 'Cv/////////////////////////////////uiRj/7IEK/+2CCv/tggr/7YIM1e6G' + + 'DfPvhgz/74YM/++HDP/vhgz/////////////////////////////////8Zw4/++G' + + 'DP/uhgz/7oYM/+6GDPPwiw7z8IsN//CLDf/wiw3/8IsN//CLDf/wiw3/////////' + + '///wig3/8IoN//CLDf/wig3/8IsN//CLDf/xjA/z85EQ1fOQD//zkA//85AP//OQ' + + 'D//zkA//85AP////////////8o8P//KPD//ykA//8o8P//KQD//ykA//85EQ1fSU' + + 'EI/1lRH/9ZUR//SUEP/1lRH/9JQQ//SUEP/+9uz///////jDev/0mRz/9ZUR//SV' + + 'Ef/1lRH/9ZUR//SUEI/5mhgr95kS/faZEv/2mRL/9pkS//aZEv/2mRL//Nqo////' + + '//////////////rLhv/3mhL/9pkS//eZEv35mhgrAAAAAPifFZT4nhT/+Z8U//ie' + + 'FP/5nxT/+Z8U//ikI//83a3//vjw//78+f/7yX3/+J4T//ieFP/4nxWUAAAAAAAA' + + 'AAD/qisG+6MWvfqjFf/6oxX/+qMV//qjFf/6oxX/+qMV//qjFf/6oxX/+qIV//qj' + + 'Ff/7oxa9/6orBgAAAAAAAAAAAAAAAP+qKwb9qRiU/agW/fyoF//8qBf//agX//yo' + + 'F//9qBf//agX//2oF/39qRiU/6orBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+y' + + 'Hiv/rRmP/6wZ1f+tGPP/rBjz/64Z1f+vGY//sh4rAAAAAAAAAAAAAAAAAAAAAPAP' + + 'AADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB' + + 'AACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+A' + + 'AAbiZQRH4GMAlf//////////////////////////4GQAv+BjAJXiZQBH/4AABgAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOpqCxjiZgKW4WYB8eJmAf/hZQH/////////' + + '///////////////////hZgH/4mYB/+FmAf/iZwHx4mYClupqCxgAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9t' + + 'JAfkagSC42kC9ONoAv/jaAL/4mgC/+NoAv///////////////////////////+Jo' + + 'Af/iaAL/4mgB/+JoAv/iaAL/42kC9ORqBIL/bSQHAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADqagsY5GoEx+NqA//jagP/42oD/+Nq' + + 'A//kawP/42oD////////////////////////////42oD/+RrA//jagP/5GsD/+Rr' + + 'A//kawP/5GsD/+RsBMfqdQsYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAA6HEGLeVuBOPlbQT/5GwD/+VtBP/kbAP/5GwD/+VtBP/kbAP/////////' + + '///////////////////kbAP/5G0D/+RsA//kbQP/5G0D/+RtA//kbQP/5G0D/+Ru' + + 'A+PocQYtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOp1CxjmcAbj5W8E/+Vv' + + 'BP/lbwT/5W8E/+VvBP/lbwT/5W8E/+VvBP///////////////////////////+Vv' + + 'BP/mcAX/5W8E/+ZwBf/mcAX/5W8E/+ZwBf/lbwT/5W8E/+ZwBuPqdQsYAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAD/kiQH53IFx+ZyBf/mcQX/5nEF/+ZxBf/mcQX/5nEF/+Zx' + + 'Bf/ncgX/5nEF////////////////////////////53IG/+ZxBf/ncgb/5nEF/+Zx' + + 'Bf/mcgX/5nEF/+ZyBf/mcgX/5nEF/+dyBcf/kiQHAAAAAAAAAAAAAAAAAAAAAOd2' + + 'CILodAb/53QG/+h0Bv/odAb/6HQG/+h0Bv/odAb/6HQG/+d0Bv/odAb/////////' + + '///////////////////ndAX/53QG/+d0Bf/ndAb/53QG/+h1Bv/ndAb/6HUG/+h1' + + 'Bv/ndAX/6HUG/+d2BoIAAAAAAAAAAAAAAADqgAsY6HYG9Oh2B//odgb/6HYH/+h2' + + 'B//odgb/6HYH/+h2Bv/odgb/6HYH/+h2Bv///////////////////////////+l3' + + 'B//odgb/6XcH/+h2Bv/odgb/6HYH/+h2Bv/odgf/6HYH/+h2Bv/odgf/6HYG9OqA' + + 'CxgAAAAAAAAAAOt6CZbpeQj/6nkI/+l5CP/qeQj/6nkI/+l5B//qeQj/6XkH/+l5' + + 'B//peQf/6XkH////////////////////////////6XkI/+l5B//peQj/6XkH/+l5' + + 'B//peQj/6XkH/+l5CP/peQj/6XkH/+l5CP/peQf/63oJlgAAAAD/gCsG7H0K8et8' + + 'Cf/qewj/63wJ/+p7CP/qewj/6nsI/+p7CP/qewj/6nsI/+t8Cf/qewj/////////' + + '///////////////////qewj/6nwJ/+p7CP/qfAn/6nwJ/+p7CP/qfAn/6nsI/+p7' + + 'CP/rfAn/6nsI/+t8Cf/sfQrx/4ArBu2BC0frfQn/630J/+t+Cf/rfQn/634J/+t+' + + 'Cf/rfgn/634J////////////////////////////////////////////////////' + + '///////////////////zs27/634J/+x+Cf/rfgn/634J/+t+Cf/rfgn/634J/+t+' + + 'Cf/tgQtH7IAKleyACv/sgAr/7IAK/+yACv/sgAr/7IAK/+yACv/sgAr/////////' + + '//////////////////////////////////////////////////////////////XC' + + 'iv/sgAr/7IAK/+yACv/sgAr/7IAJ/+yACv/sgAn/7IAJ/+yACpXugwu/7YML/+2D' + + 'C//tggr/7YML/+2CCv/tggr/7YIK/+2CCv//////////////////////////////' + + '////////////////////////////////////////+NKn/+2DC//tggr/7YML/+2D' + + 'C//tgwv/7YML/+2DC//tgwv/7oMLv++GDNnuhQv/7oUL/+6FC//uhQv/7oUL/+6F' + + 'C//vhQz/7oUL////////////////////////////////////////////////////' + + '///////////////////64cT/7oUL/+6FC//uhQv/7oUL/+6EC//uhQv/7oQL/+6E' + + 'C//vhgzZ74gO8++IDP/viAz/74cM/++IDP/vhwz/74cM/++HDP/vhwz/////////' + + '//////////////////////////////////////////////////////////////3w' + + '4f/viA3/74cM/++IDf/viA3/74cM/++IDf/vhwz/74cM/++HDfPwiw7z8IoN//CK' + + 'Df/wig3/8IoN//CKDf/wig3/8IkN//CKDf/wiQ3/8IkN//CKDf/wiQ3/////////' + + '///////////////////wiQ3/8IoN//CJDf/wig3/8IoN//CJDf/wig3/8IkN//CJ' + + 'Df/wiQ3/8IkN//CJDf/wiQ3/8IsO8/KNDtnxjA7/8YwO//GMDf/xjA7/8YwN//GM' + + 'Df/xjA3/8YwN//GMDf/xjA3/8YwO//GMDf////////////////////////////GM' + + 'Dv/xjA7/8YwO//GMDv/xjA7/8YwO//GMDv/xjA7/8YwO//GMDv/xjA7/8YwO//GM' + + 'Dv/yjQ7Z8o8Pv/KPD//yjw//8o8P//KPD//yjw//8o8P//KPD//yjw//8o8P//KP' + + 'D//yjg7/8o8P////////////////////////////8Y4O//KODv/xjg7/8o4O//KO' + + 'Dv/yjg7/8o4O//KODv/yjg7/8o8P//KODv/yjw//8o8P//OQEL/zkQ+V85EP//OR' + + 'D//zkQ//85EP//ORD//zkQ//85EP//ORD//zkQ//85EP//OREP/zkQ///vr0////' + + '///////////////////0myb/85EQ//ORD//zkRD/85EQ//ORD//zkRD/85EP//OR' + + 'D//zkQ//85EP//ORD//zkQ//85EPlfSXEkf0kxD/9JMQ//SUEP/0kxD/9JQQ//SU' + + 'EP/zkxD/9JQQ//OTEP/zkxD/9JQQ//OTEP/86tD///////////////////////rV' + + 'ov/1nSb/85MQ//STEP/0kxD/9JMQ//STEP/0kxD/9JMQ//SUEP/0kxD/9JQQ//SU' + + 'EP/0kxJH/6orBvWWEvH1lhH/9ZYR//WWEf/1lhH/9ZYR//WWEf/1lhH/9ZYR//WW' + + 'Ef/1lhH/9ZYR//vZq///////////////////////////////////////////////' + + '///1lhH/9ZYR//WWEf/1lhH/9ZYR//WWEf/1lhH/9ZYS8f+qKwYAAAAA95kTlvaY' + + 'Ev/2mBH/9pgS//aYEf/2mBH/9ZgR//aYEf/1mBH/9ZgR//aYEv/1mBH/+LFN////' + + '//////////////////////////////////////////////aYEv/1mBH/9pgS//aY' + + 'Ev/1mBH/9pgS//WYEf/3mRGWAAAAAAAAAAD/nxUY+JwU9PebE//3mxP/95sT//eb' + + 'E//3mxP/95sT//ebE//3mxP/95oS//ebE//3mhL//OK7////////////////////' + + '////////////////////////95sT//ebE//3mxP/95sT//aaEv/3mxP/95sT9P+f' + + 'FRgAAAAAAAAAAAAAAAD5nxSC+J0T//idE//4nRP/+J0T//ecE//4nRP/95wT//ec' + + 'E//4nRP/95wT//idE//4pSf//efF////////////////////////////////////' + + '///4nRP/950T//idE//4nRP/+J0T//idE//5nxSCAAAAAAAAAAAAAAAAAAAAAP+2' + + 'JAf6oBXH+aAU//mgFP/5oBT/+J8U//mgFP/4nxT/+J8U//mfFP/4nxT/+Z8U//mf' + + 'FP/5oRf/+86H//7w2v/+/Pj//v36//758f/+8+P//evQ//mgFP/5nxT/+aAU//mg' + + 'FP/4nxT/+qAVx/+2JAcAAAAAAAAAAAAAAAAAAAAAAAAAAP+qFRj7oxXj+aEU//mh' + + 'FP/6ohX/+aEU//qiFf/6ohX/+qIV//qiFf/6ohX/+qIV//mhFP/6ohX/+aEU//mh' + + 'FP/6ohX/+aEU//qiFf/6ohX/+aEU//qiFf/5oRT/+aEU//ujFeP/qhUYAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+mFi78pBXj+6QV//ukFv/7pBX/+6QW//uk' + + 'Fv/6pBX/+6QW//qkFf/6pBX/+6QW//qkFf/7pBb/+6QW//ulFv/7pBb/+6UW//ul' + + 'Fv/7pBX/+6UW//ukFf/8pBXj/6QXLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAP+qIBj8qBfH/KcW//ynF//8pxb//KcW//ynFv/8pxb//KcW//yn' + + 'Fv/7phb//KcW//umFv/7phb/+6YW//umFv/7phb/+6YW//ynFv/7phb//KgXx/+q' + + 'IBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+2' + + 'JAf9qxiC/akY9PypF//8qRf//KgX//ypF//8qBf//KgX//2pF//8qBf//akX//2p' + + 'F//9qRf//akX//2pF//9qRf//qkY9P2rGIL/tiQHAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/tSAY/60alv+s' + + 'GPH+rBj//qwY//6sGP/+rBj//asY//6sGP/9qxj//asY//2rF//9qxj//qsX8f2s' + + 'GJb/tSAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9UrBv+wGUf/rxqV/68Zv/+v' + + 'Gtn/rhnz/64Z8/+vGtn/rxm//68alf+wGUf/1SsGAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/gAAB/wAAAP4AAAB8AA' + + 'AAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAACAAAABgAAAAcAAAAPAAAAD4AAAB/AAAA/4AAAf/AAAP/8A' + + 'AP//wAP/', + ), }; _get = () => { @@ -178,7 +179,7 @@ class BlobTest extends React.Component<{...}, State> { componentDidMount() { this._get(); this._waitFor(this._getSucceeded, 6, (doneSucceeded) => { - let reader = new global.FileReader(); + let reader = new FileReader(); reader.readAsDataURL(this.state.xhr.response); reader.onload = () => { TestModule.markTestPassed( diff --git a/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js b/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js index 7fb46a949d2..4df783c41a6 100644 --- a/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js +++ b/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js @@ -14,7 +14,6 @@ /* eslint-env node */ const WebSocket = require('ws'); -const Blob = require('node-fetch'); console.log(`\ WebSocket binary integration test server