diff --git a/change/react-native-windows-11d58db5-55b8-4782-b499-1baaeb791c35.json b/change/react-native-windows-11d58db5-55b8-4782-b499-1baaeb791c35.json new file mode 100644 index 00000000000..a2aa0be10f0 --- /dev/null +++ b/change/react-native-windows-11d58db5-55b8-4782-b499-1baaeb791c35.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Handle abrupt WebSocket connection interruption", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Desktop.IntegrationTests/WebSocketIntegrationTest.cpp b/vnext/Desktop.IntegrationTests/WebSocketIntegrationTest.cpp index 6b308b6d33b..24a0be94a8a 100644 --- a/vnext/Desktop.IntegrationTests/WebSocketIntegrationTest.cpp +++ b/vnext/Desktop.IntegrationTests/WebSocketIntegrationTest.cpp @@ -385,6 +385,35 @@ TEST_CLASS (WebSocketIntegrationTest) Assert::AreEqual({}, errorMessage); Assert::AreEqual(expected, result); } + + BEGIN_TEST_METHOD_ATTRIBUTE(AbruptDisconnectFailsWithSpecificMessage) + TEST_IGNORE() //TODO: Find a way to emulate abrupt disconnection using Test::WebSocketServer + END_TEST_METHOD_ATTRIBUTE() + TEST_METHOD(AbruptDisconnectFailsWithSpecificMessage) + { + promise closedPromise; + auto ws = IWebSocketResource::Make(); + IWebSocketResource::Error error{{}, IWebSocketResource::ErrorType::None}; + CloseCode closeCode; + ws->SetOnClose([&closedPromise, &closeCode](CloseCode code, const string& reason) + { + closeCode = code; + closedPromise.set_value(); + }); + ws->SetOnError([&closedPromise, &error](IWebSocketResource::Error&& wsError) + { + error = std::move(wsError); + }); + + ws->Connect("ws://localhost:5555"); + + closedPromise.get_future().wait(); + + //NOTE: This message is implementation-specific (WinRTWebSocketResource) + Assert::AreEqual({"[0x80072EFE] Underlying TCP connection suddenly terminated"}, error.Message); + Assert::AreEqual(static_cast(IWebSocketResource::ErrorType::Connection), static_cast(error.Type)); + Assert::AreEqual(static_cast(CloseCode::BadPayload), static_cast(closeCode)); + } }; uint16_t WebSocketIntegrationTest::s_port = 6666; diff --git a/vnext/Shared/Networking/IWebSocketResource.h b/vnext/Shared/Networking/IWebSocketResource.h index 5d18b6facaf..ed7109e20a1 100644 --- a/vnext/Shared/Networking/IWebSocketResource.h +++ b/vnext/Shared/Networking/IWebSocketResource.h @@ -71,7 +71,7 @@ struct IWebSocketResource { struct Error { std::string Message; - const ErrorType Type; + ErrorType Type; }; #pragma endregion Inner types diff --git a/vnext/Shared/Networking/WinRTWebSocketResource.cpp b/vnext/Shared/Networking/WinRTWebSocketResource.cpp index 4bce0410a71..9c1a52e4833 100644 --- a/vnext/Shared/Networking/WinRTWebSocketResource.cpp +++ b/vnext/Shared/Networking/WinRTWebSocketResource.cpp @@ -316,7 +316,22 @@ void WinRTWebSocketResource::Connect(string &&url, const Protocols &protocols, c } } catch (hresult_error const &e) { if (self->m_errorHandler) { - self->m_errorHandler({Utilities::HResultToString(e), ErrorType::Receive}); + string errorMessage; + ErrorType errorType; + // See + // https://docs.microsoft.com/uwp/api/windows.networking.sockets.messagewebsocketmessagereceivedeventargs.getdatareader?view=winrt-19041#remarks + if (e.code() == WININET_E_CONNECTION_ABORTED) { + errorMessage = "[0x80072EFE] Underlying TCP connection suddenly terminated"; + errorType = ErrorType::Connection; + self->m_errorHandler({errorMessage, errorType}); + + // Note: We are not clear whether all read-related errors should close the socket. + self->Close(CloseCode::BadPayload, std::move(errorMessage)); + } else { + errorMessage = Utilities::HResultToString(e); + errorType = ErrorType::Receive; + self->m_errorHandler({errorMessage, errorType}); + } } } });