From cd93e1028bb337e0f7e83d8b704f9faa03981932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Mon, 16 Mar 2026 16:09:42 -0700 Subject: [PATCH] Fix unhandled synchronous throw from WebSocket ConnectAsync (#15783) * Fix unhandled synchronous throw from ConnectAsync (Generated summary) ConnectAsync can throw synchronously (e.g. WININET_E_INVALID_CA / 0x80072EE1 on certificate validation failure) before returning an IAsyncAction. Move the call inside the existing try/catch block so such exceptions are routed through Fail() instead of propagating unhandled. Add ConnectAsyncThrowsSynchronously unit test to cover this path. * Change files * Add explaining comment * clang format --- ...-a940bd34-f062-4109-b462-3e634bb5ddae.json | 7 ++++ .../WinRTWebSocketResourceUnitTest.cpp | 33 +++++++++++++++++++ .../Networking/WinRTWebSocketResource.cpp | 9 ++--- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 change/react-native-windows-a940bd34-f062-4109-b462-3e634bb5ddae.json diff --git a/change/react-native-windows-a940bd34-f062-4109-b462-3e634bb5ddae.json b/change/react-native-windows-a940bd34-f062-4109-b462-3e634bb5ddae.json new file mode 100644 index 00000000000..4032fccc969 --- /dev/null +++ b/change/react-native-windows-a940bd34-f062-4109-b462-3e634bb5ddae.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Fix unhandled synchronous throw from ConnectAsync", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Desktop.UnitTests/WinRTWebSocketResourceUnitTest.cpp b/vnext/Desktop.UnitTests/WinRTWebSocketResourceUnitTest.cpp index ebf8d57e303..fb4367e9103 100644 --- a/vnext/Desktop.UnitTests/WinRTWebSocketResourceUnitTest.cpp +++ b/vnext/Desktop.UnitTests/WinRTWebSocketResourceUnitTest.cpp @@ -105,6 +105,39 @@ TEST_CLASS (WinRTWebSocketResourceUnitTest) { Assert::IsFalse(connected); } + BEGIN_TEST_METHOD_ATTRIBUTE(ConnectAsyncThrowsSynchronously) + END_TEST_METHOD_ATTRIBUTE() + TEST_METHOD(ConnectAsyncThrowsSynchronously) { + Logger::WriteMessage("Microsoft::React::Test::WinRTWebSocketResourceUnitTest::ConnectAsyncThrowsSynchronously"); + bool connected = false; + string errorMessage; + promise donePromise; + + auto imws{winrt::make()}; + auto mws{imws.as()}; + // Simulate a synchronous throw from ConnectAsync (e.g. WININET_E_INVALID_CA) + // before any IAsyncAction is returned. + mws->Mocks.ConnectAsync = [](const Uri &) -> IAsyncAction { + throw winrt::hresult_error(winrt::hresult(0x80072EE1), L"Invalid CA"); + }; + + auto rc = make_shared( + std::move(imws), MockDataWriter{}, CertExceptions{}, Mso::DispatchQueue::MakeSerialQueue()); + rc->SetOnConnect([&connected]() { connected = true; }); + rc->SetOnError([&errorMessage, &donePromise](Error &&error) { + errorMessage = error.Message; + donePromise.set_value(); + }); + + rc->Connect(testUrl, {}, {}); + rc->Close(CloseCode::Normal, {}); + + donePromise.get_future().wait(); + + Assert::AreEqual({"[0x80072ee1] Invalid CA"}, errorMessage); + Assert::IsFalse(connected); + } + BEGIN_TEST_METHOD_ATTRIBUTE(SetRequestHeaderFails) END_TEST_METHOD_ATTRIBUTE() TEST_METHOD(SetRequestHeaderFails) { diff --git a/vnext/Shared/Networking/WinRTWebSocketResource.cpp b/vnext/Shared/Networking/WinRTWebSocketResource.cpp index 123fe196b67..6701b56d4ad 100644 --- a/vnext/Shared/Networking/WinRTWebSocketResource.cpp +++ b/vnext/Shared/Networking/WinRTWebSocketResource.cpp @@ -227,11 +227,12 @@ fire_and_forget WinRTWebSocketResource2::PerformConnect(Uri &&uri) noexcept { [self = self->shared_from_this(), coUri = std::move(movedUri)]() -> IAsyncAction { auto coSelf = self->shared_from_this(); - auto async = coSelf->m_socket.ConnectAsync(coUri); - co_await lessthrow_await_adapter{async}; - - auto result = async.ErrorCode(); try { + // `ConnectAsync` MAY throw synchronously (e.g. WININET_E_INVALID_CA) + auto async = coSelf->m_socket.ConnectAsync(coUri); + co_await lessthrow_await_adapter{async}; + + auto result = async.ErrorCode(); if (result >= 0) { // Non-failing HRESULT coSelf->m_readyState = ReadyState::Open;