Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Fix WebSocket binaryType handling — stop unconditional Blob interception of binary messages",
"packageName": "react-native-windows",
"email": "gordomacmaster@gmail.com",
"dependentChangeType": "patch"
}
38 changes: 38 additions & 0 deletions vnext/Shared/Modules/IWebSocketModuleContentHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,49 @@ namespace Microsoft::React {
struct IWebSocketModuleContentHandler {
virtual ~IWebSocketModuleContentHandler() noexcept {}

/// Returns true if this handler should process messages for the given socket.
/// Default returns true for backward compatibility; BlobModule overrides to
/// check whether binaryType='blob' was set for this socket via addWebSocketHandler.
///
/// WARNING: Subclasses that override Supports() with a stateful or lock-protected
/// check MUST also override both TryProcessMessage() overloads to perform the
/// check-and-process atomically. The default TryProcessMessage() calls Supports()
/// and ProcessMessage() as two separate operations with no lock held between them.
virtual bool Supports(int64_t /*socketId*/) noexcept {
return true;
}

virtual void ProcessMessage(std::string &&message, winrt::Microsoft::ReactNative::JSValueObject &params) noexcept = 0;

virtual void ProcessMessage(
std::vector<uint8_t> &&message,
winrt::Microsoft::ReactNative::JSValueObject &params) noexcept = 0;

/// Check Supports() then ProcessMessage() in one call.
/// Returns true if the message was handled.
///
/// The default implementation does NOT hold any lock across both operations.
/// Subclasses with a stateful Supports() MUST override these to make the
/// check-and-process atomic (see BlobWebSocketModuleContentHandler for an example).
virtual bool TryProcessMessage(
int64_t socketId,
std::string &&message,
winrt::Microsoft::ReactNative::JSValueObject &params) noexcept {
if (!Supports(socketId))
return false;
ProcessMessage(std::move(message), params);
return true;
}

virtual bool TryProcessMessage(
int64_t socketId,
std::vector<uint8_t> &&message,
winrt::Microsoft::ReactNative::JSValueObject &params) noexcept {
if (!Supports(socketId))
return false;
ProcessMessage(std::move(message), params);
return true;
}
};

} // namespace Microsoft::React
8 changes: 5 additions & 3 deletions vnext/Shared/Modules/WebSocketModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,20 @@ shared_ptr<IWebSocketResource> WebSocketTurboModule::CreateResource(int64_t id,
if (auto prop = propBag.Get(BlobModuleContentHandlerPropertyId()))
contentHandler = prop.Value().lock();

bool handled = false;
if (contentHandler) {
if (isBinary) {
auto buffer = CryptographicBuffer::DecodeFromBase64String(winrt::to_hstring(message));
winrt::com_array<uint8_t> arr;
CryptographicBuffer::CopyToByteArray(buffer, arr);
auto data = vector<uint8_t>(arr.begin(), arr.end());

contentHandler->ProcessMessage(std::move(data), args);
handled = contentHandler->TryProcessMessage(id, std::move(data), args);
} else {
contentHandler->ProcessMessage(string{message}, args);
handled = contentHandler->TryProcessMessage(id, string{message}, args);
}
} else {
}
if (!handled) {
args["data"] = message;
}

Expand Down
37 changes: 37 additions & 0 deletions vnext/Shared/Networking/DefaultBlobResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ BlobWebSocketModuleContentHandler::BlobWebSocketModuleContentHandler(shared_ptr<

#pragma region IWebSocketModuleContentHandler

bool BlobWebSocketModuleContentHandler::Supports(int64_t socketId) noexcept /*override*/ {
scoped_lock lock{m_mutex};
return m_socketIds.find(socketId) != m_socketIds.end();
}

void BlobWebSocketModuleContentHandler::ProcessMessage(
string &&message,
msrn::JSValueObject &params) noexcept /*override*/
Expand All @@ -241,6 +246,38 @@ void BlobWebSocketModuleContentHandler::ProcessMessage(
params[blobKeys.Type] = blobKeys.Blob;
}

bool BlobWebSocketModuleContentHandler::TryProcessMessage(
int64_t socketId,
string &&message,
msrn::JSValueObject &params) noexcept /*override*/
{
scoped_lock lock{m_mutex};
if (m_socketIds.find(socketId) == m_socketIds.end())
return false;

params[blobKeys.Data] = std::move(message);
return true;
}

bool BlobWebSocketModuleContentHandler::TryProcessMessage(
int64_t socketId,
vector<uint8_t> &&message,
msrn::JSValueObject &params) noexcept /*override*/
{
scoped_lock lock{m_mutex};
if (m_socketIds.find(socketId) == m_socketIds.end())
return false;

auto blob = msrn::JSValueObject{
{blobKeys.Offset, 0},
{blobKeys.Size, message.size()},
{blobKeys.BlobId, m_blobPersistor->StoreMessage(std::move(message))}};

params[blobKeys.Data] = std::move(blob);
params[blobKeys.Type] = blobKeys.Blob;
return true;
}

#pragma endregion IWebSocketModuleContentHandler

void BlobWebSocketModuleContentHandler::Register(int64_t socketID) noexcept {
Expand Down
12 changes: 12 additions & 0 deletions vnext/Shared/Networking/DefaultBlobResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,23 @@ class BlobWebSocketModuleContentHandler final : public IWebSocketModuleContentHa

#pragma region IWebSocketModuleContentHandler

bool Supports(int64_t socketId) noexcept override;

void ProcessMessage(std::string &&message, winrt::Microsoft::ReactNative::JSValueObject &params) noexcept override;

void ProcessMessage(std::vector<uint8_t> &&message, winrt::Microsoft::ReactNative::JSValueObject &params) noexcept
override;

bool TryProcessMessage(
int64_t socketId,
std::string &&message,
winrt::Microsoft::ReactNative::JSValueObject &params) noexcept override;

bool TryProcessMessage(
int64_t socketId,
std::vector<uint8_t> &&message,
winrt::Microsoft::ReactNative::JSValueObject &params) noexcept override;

#pragma endregion IWebSocketModuleContentHandler

void Register(int64_t socketID) noexcept;
Expand Down