From 1f31d126b5f2772eab402434a9467ce890413485 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 16 Sep 2020 19:13:03 -0700 Subject: [PATCH 01/19] Declare BlobModule --- vnext/Desktop/Modules/BlobModule.cpp | 96 +++++++++++++++++++ vnext/Desktop/Modules/BlobModule.h | 43 +++++++++ vnext/Desktop/NativeModuleFactories.h | 2 + vnext/Desktop/React.Windows.Desktop.vcxproj | 2 + .../React.Windows.Desktop.vcxproj.filters | 6 ++ vnext/Shared/Shared.vcxitems.filters | 2 +- 6 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 vnext/Desktop/Modules/BlobModule.cpp create mode 100644 vnext/Desktop/Modules/BlobModule.h diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp new file mode 100644 index 00000000000..5cc019b1baa --- /dev/null +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "BlobModule.h" + +#include +#include + +using namespace facebook::xplat; + +using folly::dynamic; +using std::shared_ptr; +using std::string; + +namespace { +constexpr char moduleName[] = "BlobModule"; +} + +namespace Microsoft::React { + +#pragma region CxxModule overrides + +string BlobModule::getName() +{ + return moduleName; +} + +std::map BlobModule::getConstants() { + return {}; +} + +// clang-format off +std::vector BlobModule::getMethods() { + return + { + Method( + "addNetworkingHandler", + [this](dynamic args) + { + + } + ), + + Method( + "addWebSocketHandler", + [this](dynamic args) + { + auto id = jsArgAsInt(args, 0); + } + ), + + Method( + "removeWebSocketHandler", + [this](dynamic args) + { + auto id = jsArgAsInt(args, 0); + } + ), + + Method( + "sendOverSocket", + [this](dynamic args) + { + auto blob = jsArgAsObject(args, 0); + auto socketID = jsArgAsInt(args, 1); + } + ), + + Method( + "createFromParts", + [this](dynamic args) + { + auto parts = jsArgAsArray(args, 0); // Array + auto withId = jsArgAsString(args, 1); + } + ), + + Method( + "release", + [this](dynamic args) // blobId: string + { + auto blobId = jsArgAsString(args, 0); + } + ) + }; +} +// clang-format on + +#pragma endregion CxxModule overrides + +/*extern*/ std::unique_ptr CreateBlobModule() noexcept +{ + return std::make_unique(); +} + +} // namespace Microsoft::React diff --git a/vnext/Desktop/Modules/BlobModule.h b/vnext/Desktop/Modules/BlobModule.h new file mode 100644 index 00000000000..cb3e7dd2d26 --- /dev/null +++ b/vnext/Desktop/Modules/BlobModule.h @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include + +namespace Microsoft::React { + +class BlobModule : public facebook::xplat::module::CxxModule { + public: + enum MethodId { + AddNetworkingHandler = 0, + AddWebSocketHandler = 1, + RemoveWebSocketHandler = 2, + SendOverSocket = 3, + CreateFromParts = 4, + Release = 5, + SIZE = 6 + }; + +#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 +}; + +} // namespace Microsoft::React diff --git a/vnext/Desktop/NativeModuleFactories.h b/vnext/Desktop/NativeModuleFactories.h index babe17f4e03..8e4a2e5118a 100644 --- a/vnext/Desktop/NativeModuleFactories.h +++ b/vnext/Desktop/NativeModuleFactories.h @@ -18,5 +18,7 @@ extern std::unique_ptr CreateTimingModule( extern std::unique_ptr CreateWebSocketModule() noexcept; +extern std::unique_ptr CreateBlobModule() noexcept; + } // namespace react } // namespace facebook diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj index 607956bdc9c..3be9d93dc4b 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj @@ -216,6 +216,7 @@ + @@ -284,6 +285,7 @@ + diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters index 24338eb9a51..10ea6adcc36 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters @@ -164,6 +164,9 @@ Source Files + + Source Files\Modules + @@ -184,6 +187,9 @@ Header Files + + Header Files\Modules + diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index b9c58bf92f7..4bc999a0f8b 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -321,4 +321,4 @@ Source Files\etw - + \ No newline at end of file From 59c2d8e55fe518f903d938041d7c6e133c57d6a9 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 18 Sep 2020 16:54:35 -0700 Subject: [PATCH 02/19] Add WebSocketBlob integration test --- .../RNTesterIntegrationTests.cpp | 8 + .../src/IntegrationTests/WebSocketBlobTest.js | 173 ++++++++++++++++++ .../websocket_integration_test_server_blob.js | 34 ++++ 3 files changed, 215 insertions(+) create mode 100644 vnext/src/IntegrationTests/WebSocketBlobTest.js create mode 100644 vnext/src/IntegrationTests/websocket_integration_test_server_blob.js diff --git a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp index 5ae6ee31190..71545168be6 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp @@ -199,5 +199,13 @@ TEST_CLASS (RNTesterIntegrationTests) { Assert::AreEqual(TestStatus::Passed, result.Status, result.Message.c_str()); } + BEGIN_TEST_METHOD_ATTRIBUTE(WebSocketBlob) + TEST_IGNORE() + END_TEST_METHOD_ATTRIBUTE() + TEST_METHOD(WebSocketBlob) { + auto result = m_runner.RunTest("IntegrationTests/WebSocketBlobTest", "WebSocketBlobTest"); + Assert::AreEqual(TestStatus::Passed, result.Status, result.Message.c_str()); + } + #pragma endregion Extended Tests }; diff --git a/vnext/src/IntegrationTests/WebSocketBlobTest.js b/vnext/src/IntegrationTests/WebSocketBlobTest.js new file mode 100644 index 00000000000..e74af7d8659 --- /dev/null +++ b/vnext/src/IntegrationTests/WebSocketBlobTest.js @@ -0,0 +1,173 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const {View} = ReactNative; +const {TestModule} = ReactNative.NativeModules; + +const DEFAULT_WS_URL = 'ws://localhost:5557/'; + +const WS_EVENTS = ['close', 'error', 'message', 'open']; + +type State = { + url: string, + fetchStatus: ?string, + socket: ?WebSocket, + socketState: ?number, + lastSocketEvent: ?string, + lastMessage: ?Blob, + testMessage: Uint8Array, + testExpectedResponse: Uint8Array, + ... +}; + +class WebSocketBinaryTest extends React.Component<{}, State> { + state: State = { + url: DEFAULT_WS_URL, + fetchStatus: null, + socket: null, + socketState: null, + lastSocketEvent: null, + lastMessage: null, + testMessage: new Uint8Array([1, 2, 3]), + testExpectedResponse: new Uint8Array([4, 5, 6, 7]), + }; + + _waitFor = (condition: any, timeout: any, callback: any) => { + let remaining = timeout; + const timeoutFunction = function() { + if (condition()) { + callback(true); + return; + } + remaining--; + if (remaining === 0) { + callback(false); + } else { + setTimeout(timeoutFunction, 1000); + } + }; + setTimeout(timeoutFunction, 1000); + }; + + _connect = () => { + const socket = new WebSocket(this.state.url); + socket.binaryType = 'blob'; + WS_EVENTS.forEach(ev => socket.addEventListener(ev, this._onSocketEvent)); + this.setState({ + socket, + socketState: socket.readyState, + }); + }; + + _socketIsConnected = () => { + return this.state.socketState === 1; //'OPEN' + }; + + _socketIsDisconnected = () => { + return this.state.socketState === 3; //'CLOSED' + }; + + _disconnect = () => { + if (!this.state.socket) { + return; + } + this.state.socket.close(); + }; + + _onSocketEvent = (event: any) => { + const state: any = { + socketState: event.target.readyState, + lastSocketEvent: event.type, + }; + if (event.type === 'message') { + state.lastMessage = event.data; + } + this.setState(state); + }; + + _sendBinary = (message: Blob) => { + if (!this.state.socket) { + return; + } + this.state.socket.send(message); + }; + + _sendTestMessage = () => { + this._sendBinary(this.state.testMessage); + }; + + _receivedTestExpectedResponse = () => { + console.warn(this.state.lastMessage?.size); + console.warn('bat'); + console.warn(this.state.testExpectedResponse.length); + + if ( + this.state.lastMessage?.size !== + this.state.testExpectedResponse.length + ) { + return false; + } + +// for (var i = 0; i < expected.length; i++) { +// if (expected[i] !== result[i]) { +// return false; +// } +// } +// + return true; + }; + + componentDidMount() { + console.log('mounted') + console.warn('mounted') + this.testConnect(); + } + + testConnect: () => void = () => { + this._connect(); + this._waitFor(this._socketIsConnected, 5, connectSucceeded => { + if (!connectSucceeded) { + TestModule.markTestPassed(false); + return; + } + this.testSendAndReceive(); + }); + }; + + testSendAndReceive: () => void = () => { + this._sendTestMessage(); + this._waitFor(this._receivedTestExpectedResponse, 5, messageReceived => { + if (!messageReceived) { + TestModule.markTestPassed(false); + return; + } + this.testDisconnect(); + }); + }; + + testDisconnect: () => void = () => { + this._disconnect(); + this._waitFor(this._socketIsDisconnected, 5, disconnectSucceeded => { + TestModule.markTestPassed(disconnectSucceeded); + }); + }; + + render(): React.Node { + return ; + } +} + +WebSocketBinaryTest.displayName = 'WebSocketBinaryTest'; + +module.exports = WebSocketBinaryTest; diff --git a/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js b/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js new file mode 100644 index 00000000000..f415043d917 --- /dev/null +++ b/vnext/src/IntegrationTests/websocket_integration_test_server_blob.js @@ -0,0 +1,34 @@ +#!/usr/bin/env node +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +/* eslint-env node */ + +const WebSocket = require('ws'); +const Blob = require('node-fetch'); + +console.log(`\ +WebSocket binary integration test server + +This will send each incoming message back, in binary form. + +`); + +const server = new WebSocket.Server({port: 5557}); +server.on('connection', ws => { + ws.binaryType = "blob"; + ws.on('message', message => { + console.log(message); + + ws.send([4, 5, 6, 7]); + }); +}); From 646c5dd9feb1fbdeb09681c217879e2f1ed7cf05 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 21 Sep 2020 20:12:30 -0700 Subject: [PATCH 03/19] Declare IWebSocketContentHandler --- vnext/Desktop/IWebSocketContentHandler.h | 26 +++++++++++++++++ vnext/Desktop/Modules/BlobModule.cpp | 28 +++++++++++++++++++ vnext/Desktop/Modules/BlobModule.h | 22 ++++++++++++++- vnext/Desktop/React.Windows.Desktop.vcxproj | 1 + .../React.Windows.Desktop.vcxproj.filters | 3 ++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 vnext/Desktop/IWebSocketContentHandler.h diff --git a/vnext/Desktop/IWebSocketContentHandler.h b/vnext/Desktop/IWebSocketContentHandler.h new file mode 100644 index 00000000000..6c3b7dfd8fb --- /dev/null +++ b/vnext/Desktop/IWebSocketContentHandler.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +// React Native +#include + +// Standard Library +#include +#include + +namespace Microsoft::React { + +/// +/// See https://github.com/facebook/react-native/blob/v0.63.2/React/CoreModules/RCTWebSocketModule.h#L12 +/// +struct IWebSocketContentHandler { + + virtual void ProcessMessage(std::string&& message, folly::dynamic& params) = 0; + + virtual void ProcessMessage(std::vector&& message, folly::dynamic ¶ms) = 0; + +}; + +} // namespace Microsoft::React diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 5cc019b1baa..89fa40e21e4 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -9,8 +9,11 @@ using namespace facebook::xplat; using folly::dynamic; +using std::lock_guard; +using std::mutex; using std::shared_ptr; using std::string; +using std::vector; namespace { constexpr char moduleName[] = "BlobModule"; @@ -88,6 +91,31 @@ std::vector BlobModule::getMethods() { #pragma endregion CxxModule overrides +#pragma region IWebSocketContentHandler overrides + +void BlobModule::ProcessMessage(string&& message, dynamic& params) /*override*/ +{ + params["data"] = std::move(message); +} + +void BlobModule::ProcessMessage(vector&& message, dynamic ¶ms) /*override*/ +{ + auto blob = dynamic::object(); + blob("offset", 0); + blob("size", message.size()); + + string blobId = "F2A3D3C2-BAFA-4298-A8FF-5A35F35FADB8";//TODO: Generate + { + lock_guard lock{m_blobsMutex}; + //m_blobs.insert_or_assign(std::move(blobId), ) + } + + params["data"] = std::move(blob); + params["type"] = "blob"; +} + +#pragma endregion IWebSocketContentHandler overrides + /*extern*/ std::unique_ptr CreateBlobModule() noexcept { return std::make_unique(); diff --git a/vnext/Desktop/Modules/BlobModule.h b/vnext/Desktop/Modules/BlobModule.h index cb3e7dd2d26..722b37454d6 100644 --- a/vnext/Desktop/Modules/BlobModule.h +++ b/vnext/Desktop/Modules/BlobModule.h @@ -3,11 +3,23 @@ #pragma once +#include + +// React Native #include +// Standard Library +#include +#include +#include +#include + namespace Microsoft::React { -class BlobModule : public facebook::xplat::module::CxxModule { +class BlobModule : public facebook::xplat::module::CxxModule, IWebSocketContentHandler { + std::unordered_map> m_blobs; + std::mutex m_blobsMutex; + public: enum MethodId { AddNetworkingHandler = 0, @@ -38,6 +50,14 @@ class BlobModule : public facebook::xplat::module::CxxModule { std::vector getMethods() override; #pragma endregion CxxModule overrides + +#pragma region IWebSocketContentHandler overrides + + void ProcessMessage(std::string&& message, folly::dynamic ¶ms) override; + + void ProcessMessage(std::vector&& message, folly::dynamic ¶ms) override; + +#pragma endregion IWebSocketContentHandler overrides }; } // namespace Microsoft::React diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj index 3be9d93dc4b..26fad41cdee 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj @@ -285,6 +285,7 @@ + diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters index 10ea6adcc36..0c9ea5c9e69 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters @@ -190,6 +190,9 @@ Header Files\Modules + + Header Files + From eedf797b04b38dffd90f2c0748132817e4e0e28b Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 21 Sep 2020 20:12:52 -0700 Subject: [PATCH 04/19] Temporarily use web debugger. --- vnext/IntegrationTests/TestRunner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnext/IntegrationTests/TestRunner.cpp b/vnext/IntegrationTests/TestRunner.cpp index e57e60ae0b8..30bd803cddb 100644 --- a/vnext/IntegrationTests/TestRunner.cpp +++ b/vnext/IntegrationTests/TestRunner.cpp @@ -69,7 +69,7 @@ TestResult TestRunner::RunTest(string &&bundlePath, string &&appName, NativeLogg // Note, further configuration should be done in each Windows variant's // TestRunner implementation. shared_ptr devSettings = make_shared(); - devSettings->useWebDebugger = false; // WebSocketJSExecutor can't register native log hooks. + devSettings->useWebDebugger = true; // WebSocketJSExecutor can't register native log hooks.//TODO: Revert! devSettings->liveReloadCallback = []() {}; // Enables ChakraExecutor devSettings->errorCallback = [&result](string message) { result.Message = Microsoft::Common::Unicode::Utf8ToUtf16(message); From 2c5fb2ff0fccf4da99bf92d525948908361e4b43 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 21 Sep 2020 20:14:24 -0700 Subject: [PATCH 05/19] Insert blob data --- vnext/Desktop/Modules/BlobModule.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 89fa40e21e4..19968a2013c 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -104,10 +104,11 @@ void BlobModule::ProcessMessage(vector&& message, dynamic ¶ms) /*ov blob("offset", 0); blob("size", message.size()); + // Equivalent to store()... string blobId = "F2A3D3C2-BAFA-4298-A8FF-5A35F35FADB8";//TODO: Generate { lock_guard lock{m_blobsMutex}; - //m_blobs.insert_or_assign(std::move(blobId), ) + m_blobs.insert_or_assign(blobId, std::move(message)); } params["data"] = std::move(blob); From 98fa1778dff9688549a459b10f953417d701cc29 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 22 Sep 2020 15:03:29 -0700 Subject: [PATCH 06/19] Generate blob GUID --- vnext/Desktop/Modules/BlobModule.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 19968a2013c..b2baf8473f5 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -3,9 +3,13 @@ #include "BlobModule.h" +// React Native #include #include +// Windows API +#include + using namespace facebook::xplat; using folly::dynamic; @@ -14,6 +18,7 @@ using std::mutex; using std::shared_ptr; using std::string; using std::vector; +using winrt::Windows::Foundation::GuidHelper; namespace { constexpr char moduleName[] = "BlobModule"; @@ -104,8 +109,9 @@ void BlobModule::ProcessMessage(vector&& message, dynamic ¶ms) /*ov blob("offset", 0); blob("size", message.size()); - // Equivalent to store()... - string blobId = "F2A3D3C2-BAFA-4298-A8FF-5A35F35FADB8";//TODO: Generate + // Equivalent to store() + // substr(1, 36) strips curly braces in a GUID. + string blobId = winrt::to_string(winrt::to_hstring(GuidHelper::CreateNewGuid())).substr(1, 36); { lock_guard lock{m_blobsMutex}; m_blobs.insert_or_assign(blobId, std::move(message)); From 92c8a0b062765cb9e9c9b5054036e9fae373c190 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 22 Sep 2020 16:32:08 -0700 Subject: [PATCH 07/19] Define IWebSocketModule. --- vnext/Desktop/IWebSocketContentHandler.h | 26 ----------------- vnext/Desktop/Modules/BlobModule.cpp | 8 ++--- vnext/Desktop/Modules/BlobModule.h | 8 ++--- vnext/Desktop/React.Windows.Desktop.vcxproj | 1 - .../React.Windows.Desktop.vcxproj.filters | 3 -- vnext/Shared/Modules/IWebSocketModule.h | 29 +++++++++++++++++++ vnext/Shared/Shared.vcxitems | 1 + vnext/Shared/Shared.vcxitems.filters | 1 + 8 files changed, 39 insertions(+), 38 deletions(-) delete mode 100644 vnext/Desktop/IWebSocketContentHandler.h create mode 100644 vnext/Shared/Modules/IWebSocketModule.h diff --git a/vnext/Desktop/IWebSocketContentHandler.h b/vnext/Desktop/IWebSocketContentHandler.h deleted file mode 100644 index 6c3b7dfd8fb..00000000000 --- a/vnext/Desktop/IWebSocketContentHandler.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -// React Native -#include - -// Standard Library -#include -#include - -namespace Microsoft::React { - -/// -/// See https://github.com/facebook/react-native/blob/v0.63.2/React/CoreModules/RCTWebSocketModule.h#L12 -/// -struct IWebSocketContentHandler { - - virtual void ProcessMessage(std::string&& message, folly::dynamic& params) = 0; - - virtual void ProcessMessage(std::vector&& message, folly::dynamic ¶ms) = 0; - -}; - -} // namespace Microsoft::React diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index b2baf8473f5..76a8674d51b 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -6,6 +6,7 @@ // React Native #include #include +#include // Windows API #include @@ -45,7 +46,6 @@ std::vector BlobModule::getMethods() { "addNetworkingHandler", [this](dynamic args) { - } ), @@ -96,7 +96,7 @@ std::vector BlobModule::getMethods() { #pragma endregion CxxModule overrides -#pragma region IWebSocketContentHandler overrides +#pragma region IWebSocketModule::ContentHandler overrides void BlobModule::ProcessMessage(string&& message, dynamic& params) /*override*/ { @@ -110,7 +110,7 @@ void BlobModule::ProcessMessage(vector&& message, dynamic ¶ms) /*ov blob("size", message.size()); // Equivalent to store() - // substr(1, 36) strips curly braces in a GUID. + // substr(1, 36) strips curly braces from a GUID. string blobId = winrt::to_string(winrt::to_hstring(GuidHelper::CreateNewGuid())).substr(1, 36); { lock_guard lock{m_blobsMutex}; @@ -121,7 +121,7 @@ void BlobModule::ProcessMessage(vector&& message, dynamic ¶ms) /*ov params["type"] = "blob"; } -#pragma endregion IWebSocketContentHandler overrides +#pragma endregion IWebSocketModule::ContentHandler overrides /*extern*/ std::unique_ptr CreateBlobModule() noexcept { diff --git a/vnext/Desktop/Modules/BlobModule.h b/vnext/Desktop/Modules/BlobModule.h index 722b37454d6..34f69681b7b 100644 --- a/vnext/Desktop/Modules/BlobModule.h +++ b/vnext/Desktop/Modules/BlobModule.h @@ -3,7 +3,7 @@ #pragma once -#include +#include // React Native #include @@ -16,7 +16,7 @@ namespace Microsoft::React { -class BlobModule : public facebook::xplat::module::CxxModule, IWebSocketContentHandler { +class BlobModule : public facebook::xplat::module::CxxModule, IWebSocketModule::ContentHandler { std::unordered_map> m_blobs; std::mutex m_blobsMutex; @@ -51,13 +51,13 @@ class BlobModule : public facebook::xplat::module::CxxModule, IWebSocketContentH #pragma endregion CxxModule overrides -#pragma region IWebSocketContentHandler overrides +#pragma region IWebSocketModule::ContentHandler overrides void ProcessMessage(std::string&& message, folly::dynamic ¶ms) override; void ProcessMessage(std::vector&& message, folly::dynamic ¶ms) override; -#pragma endregion IWebSocketContentHandler overrides +#pragma endregion IWebSocketModule::ContentHandler overrides }; } // namespace Microsoft::React diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj index 26fad41cdee..3be9d93dc4b 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj @@ -285,7 +285,6 @@ - diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters index 0c9ea5c9e69..10ea6adcc36 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters @@ -190,9 +190,6 @@ Header Files\Modules - - Header Files - diff --git a/vnext/Shared/Modules/IWebSocketModule.h b/vnext/Shared/Modules/IWebSocketModule.h new file mode 100644 index 00000000000..4c71bfaa339 --- /dev/null +++ b/vnext/Shared/Modules/IWebSocketModule.h @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +// React Native +#include + +// Standard Library +#include +#include + +namespace Microsoft::React { + +struct IWebSocketModule +{ + + /// + /// See https://github.com/facebook/react-native/blob/v0.63.2/React/CoreModules/RCTWebSocketModule.h#L12 + /// + struct ContentHandler + { + virtual void ProcessMessage(std::string &&message, folly::dynamic ¶ms) = 0; + + virtual void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) = 0; + }; +}; + +} // namespace Microsoft::React diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 45c3a1624eb..9090addbea0 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -64,6 +64,7 @@ + diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 4bc999a0f8b..040a707b2c1 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -306,6 +306,7 @@ Header Files + From 79f8ffb2c3d6054864647c796698966ff7e3d518 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 22 Sep 2020 18:50:35 -0700 Subject: [PATCH 08/19] BlobModule::getConstants --- vnext/Desktop/Modules/BlobModule.cpp | 5 +++-- vnext/Shared/Modules/IWebSocketModule.h | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 76a8674d51b..8b707245d41 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -23,7 +23,8 @@ using winrt::Windows::Foundation::GuidHelper; namespace { constexpr char moduleName[] = "BlobModule"; -} +constexpr char blobURIScheme[] = "blob"; +} // namespace namespace Microsoft::React { @@ -35,7 +36,7 @@ string BlobModule::getName() } std::map BlobModule::getConstants() { - return {}; + return {{"BLOB_URI_SCHEME", blobURIScheme}, {"BLOB_URI_HOST", {}}}; } // clang-format off diff --git a/vnext/Shared/Modules/IWebSocketModule.h b/vnext/Shared/Modules/IWebSocketModule.h index 4c71bfaa339..e5f9a559848 100644 --- a/vnext/Shared/Modules/IWebSocketModule.h +++ b/vnext/Shared/Modules/IWebSocketModule.h @@ -14,7 +14,6 @@ namespace Microsoft::React { struct IWebSocketModule { - /// /// See https://github.com/facebook/react-native/blob/v0.63.2/React/CoreModules/RCTWebSocketModule.h#L12 /// From 2c03618e06e1a65a06f6d711d5f6cf156b86d59d Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 22 Sep 2020 21:54:30 -0700 Subject: [PATCH 09/19] Define BlobWebSocketModuleContentHandler. --- vnext/Desktop/Modules/BlobModule.cpp | 39 +++++++++++++++++++ vnext/Desktop/Modules/BlobModule.h | 20 ++++++++++ .../Modules/IWebSocketModuleContentHandler.h | 31 +++++++++++++++ vnext/Shared/Shared.vcxitems | 1 + vnext/Shared/Shared.vcxitems.filters | 1 + 5 files changed, 92 insertions(+) create mode 100644 vnext/Shared/Modules/IWebSocketModuleContentHandler.h diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 8b707245d41..88203f6be5a 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -24,10 +24,18 @@ using winrt::Windows::Foundation::GuidHelper; namespace { constexpr char moduleName[] = "BlobModule"; constexpr char blobURIScheme[] = "blob"; + +//TODO: Check for leaks. +shared_ptr wsContentHandler; } // namespace namespace Microsoft::React { +BlobModule::BlobModule() noexcept { + if (!wsContentHandler) + wsContentHandler = std::make_shared(); +} + #pragma region CxxModule overrides string BlobModule::getName() @@ -124,6 +132,37 @@ void BlobModule::ProcessMessage(vector&& message, dynamic ¶ms) /*ov #pragma endregion IWebSocketModule::ContentHandler overrides +#pragma region IWebSocketModuleContentHandler overrides + +void BlobWebSocketModuleContentHandler::ProcessMessage(string&& message, dynamic& params) /*override*/ +{ + params["data"] = std::move(message); +} + +void BlobWebSocketModuleContentHandler::ProcessMessage(vector &&message, dynamic ¶ms) /*override*/ +{ + auto blob = dynamic::object(); + blob("offset", 0); + blob("size", message.size()); + + // Equivalent to store() + // substr(1, 36) strips curly braces from a GUID. + string blobId = winrt::to_string(winrt::to_hstring(GuidHelper::CreateNewGuid())).substr(1, 36); + { + lock_guard lock{m_blobsMutex}; + m_blobs.insert_or_assign(blobId, std::move(message)); + } + + params["data"] = std::move(blob); + params["type"] = "blob"; +} + +#pragma endregion IWebSocketModuleContentHandler overrides + +/*static*/ shared_ptr IWebSocketModuleContentHandler::GetInstance() noexcept { + return wsContentHandler; +} + /*extern*/ std::unique_ptr CreateBlobModule() noexcept { return std::make_unique(); diff --git a/vnext/Desktop/Modules/BlobModule.h b/vnext/Desktop/Modules/BlobModule.h index 34f69681b7b..497ad7efdf2 100644 --- a/vnext/Desktop/Modules/BlobModule.h +++ b/vnext/Desktop/Modules/BlobModule.h @@ -4,6 +4,7 @@ #pragma once #include +#include // React Native #include @@ -31,6 +32,8 @@ class BlobModule : public facebook::xplat::module::CxxModule, IWebSocketModule:: SIZE = 6 }; + BlobModule() noexcept; + #pragma region CxxModule overrides /// @@ -60,4 +63,21 @@ class BlobModule : public facebook::xplat::module::CxxModule, IWebSocketModule:: #pragma endregion IWebSocketModule::ContentHandler overrides }; +class BlobWebSocketModuleContentHandler : public IWebSocketModuleContentHandler { + + std::unordered_map> m_blobs; + std::mutex m_blobsMutex; + +public: + +#pragma region IWebSocketModuleContentHandler overrides + + void ProcessMessage(std::string &&message, folly::dynamic ¶ms) override; + + void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) override; + +#pragma endregion IWebSocketModuleContentHandler overrides + +}; + } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h new file mode 100644 index 00000000000..e087ed9bc7d --- /dev/null +++ b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +// React Native +#include + +// Standard Library +#include +#include + +namespace Microsoft::React { + +/// +/// See https://github.com/facebook/react-native/blob/v0.63.2/React/CoreModules/RCTWebSocketModule.h#L12 +/// +struct IWebSocketModuleContentHandler { + + //static void SetInstance(std::shared_ptr); + + static std::shared_ptr GetInstance() noexcept; + + virtual ~IWebSocketModuleContentHandler() noexcept {} + + virtual void ProcessMessage(std::string &&message, folly::dynamic ¶ms) = 0; + + virtual void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) = 0; +}; + +} // namespace Microsoft::React diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 9090addbea0..483b1034094 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -65,6 +65,7 @@ + diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 040a707b2c1..e28e9721866 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -307,6 +307,7 @@ Header Files + From 168f11648507d1a40a4a85eed5e45f9279c6407d Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 22 Sep 2020 21:59:12 -0700 Subject: [PATCH 10/19] Remove IWebSocketModule --- vnext/Desktop/Modules/BlobModule.cpp | 29 +------------------------ vnext/Desktop/Modules/BlobModule.h | 11 +--------- vnext/Shared/Modules/IWebSocketModule.h | 28 ------------------------ vnext/Shared/Shared.vcxitems | 1 - vnext/Shared/Shared.vcxitems.filters | 1 - 5 files changed, 2 insertions(+), 68 deletions(-) delete mode 100644 vnext/Shared/Modules/IWebSocketModule.h diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 88203f6be5a..7f99dd235d7 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -25,7 +25,7 @@ namespace { constexpr char moduleName[] = "BlobModule"; constexpr char blobURIScheme[] = "blob"; -//TODO: Check for leaks. +//TODO: Check for memory leaks. shared_ptr wsContentHandler; } // namespace @@ -105,33 +105,6 @@ std::vector BlobModule::getMethods() { #pragma endregion CxxModule overrides -#pragma region IWebSocketModule::ContentHandler overrides - -void BlobModule::ProcessMessage(string&& message, dynamic& params) /*override*/ -{ - params["data"] = std::move(message); -} - -void BlobModule::ProcessMessage(vector&& message, dynamic ¶ms) /*override*/ -{ - auto blob = dynamic::object(); - blob("offset", 0); - blob("size", message.size()); - - // Equivalent to store() - // substr(1, 36) strips curly braces from a GUID. - string blobId = winrt::to_string(winrt::to_hstring(GuidHelper::CreateNewGuid())).substr(1, 36); - { - lock_guard lock{m_blobsMutex}; - m_blobs.insert_or_assign(blobId, std::move(message)); - } - - params["data"] = std::move(blob); - params["type"] = "blob"; -} - -#pragma endregion IWebSocketModule::ContentHandler overrides - #pragma region IWebSocketModuleContentHandler overrides void BlobWebSocketModuleContentHandler::ProcessMessage(string&& message, dynamic& params) /*override*/ diff --git a/vnext/Desktop/Modules/BlobModule.h b/vnext/Desktop/Modules/BlobModule.h index 497ad7efdf2..e57e1ad4245 100644 --- a/vnext/Desktop/Modules/BlobModule.h +++ b/vnext/Desktop/Modules/BlobModule.h @@ -3,7 +3,6 @@ #pragma once -#include #include // React Native @@ -17,7 +16,7 @@ namespace Microsoft::React { -class BlobModule : public facebook::xplat::module::CxxModule, IWebSocketModule::ContentHandler { +class BlobModule : public facebook::xplat::module::CxxModule { std::unordered_map> m_blobs; std::mutex m_blobsMutex; @@ -53,14 +52,6 @@ class BlobModule : public facebook::xplat::module::CxxModule, IWebSocketModule:: std::vector getMethods() override; #pragma endregion CxxModule overrides - -#pragma region IWebSocketModule::ContentHandler overrides - - void ProcessMessage(std::string&& message, folly::dynamic ¶ms) override; - - void ProcessMessage(std::vector&& message, folly::dynamic ¶ms) override; - -#pragma endregion IWebSocketModule::ContentHandler overrides }; class BlobWebSocketModuleContentHandler : public IWebSocketModuleContentHandler { diff --git a/vnext/Shared/Modules/IWebSocketModule.h b/vnext/Shared/Modules/IWebSocketModule.h deleted file mode 100644 index e5f9a559848..00000000000 --- a/vnext/Shared/Modules/IWebSocketModule.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -// React Native -#include - -// Standard Library -#include -#include - -namespace Microsoft::React { - -struct IWebSocketModule -{ - /// - /// See https://github.com/facebook/react-native/blob/v0.63.2/React/CoreModules/RCTWebSocketModule.h#L12 - /// - struct ContentHandler - { - virtual void ProcessMessage(std::string &&message, folly::dynamic ¶ms) = 0; - - virtual void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) = 0; - }; -}; - -} // namespace Microsoft::React diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 483b1034094..7b45e37ad60 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -64,7 +64,6 @@ - diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index e28e9721866..a0885614bfe 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -306,7 +306,6 @@ Header Files - From 62aebbaf7ee9461356b2d12a6482df5fcec9e989 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 23 Sep 2020 00:55:34 -0700 Subject: [PATCH 11/19] Finish implementing WS content handler --- vnext/Desktop/Modules/BlobModule.cpp | 66 ++++++++++++++++++- vnext/Desktop/Modules/BlobModule.h | 18 +++-- .../Modules/IWebSocketModuleContentHandler.h | 13 +++- vnext/Shared/Modules/WebSocketModule.h | 2 +- 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 7f99dd235d7..60424813396 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -3,6 +3,8 @@ #include "BlobModule.h" +#include + // React Native #include #include @@ -10,6 +12,7 @@ // Windows API #include +#include using namespace facebook::xplat; @@ -20,6 +23,7 @@ using std::shared_ptr; using std::string; using std::vector; using winrt::Windows::Foundation::GuidHelper; +using winrt::Windows::Security::Cryptography::CryptographicBuffer; namespace { constexpr char moduleName[] = "BlobModule"; @@ -32,8 +36,7 @@ shared_ptr wsContentHandler; namespace Microsoft::React { BlobModule::BlobModule() noexcept { - if (!wsContentHandler) - wsContentHandler = std::make_shared(); + wsContentHandler = std::make_shared(); } #pragma region CxxModule overrides @@ -63,6 +66,8 @@ std::vector BlobModule::getMethods() { [this](dynamic args) { auto id = jsArgAsInt(args, 0); + + IWebSocketModuleContentHandler::GetInstance()->Register(id); } ), @@ -71,6 +76,8 @@ std::vector BlobModule::getMethods() { [this](dynamic args) { auto id = jsArgAsInt(args, 0); + + IWebSocketModuleContentHandler::GetInstance()->Unregister(id); } ), @@ -79,7 +86,22 @@ std::vector BlobModule::getMethods() { [this](dynamic args) { auto blob = jsArgAsObject(args, 0); + auto blobId = blob["blobId"].getString(); + auto offset = blob["offset"].getInt(); + auto size = blob["size"].getInt(); auto socketID = jsArgAsInt(args, 1); + + auto data = IWebSocketModuleContentHandler::GetInstance()->ResolveMessage(std::move(blobId), offset, size); + + if (auto instance = getInstance().lock()) + { + auto buffer = CryptographicBuffer::CreateFromByteArray(data); + auto winrtString = CryptographicBuffer::EncodeToBase64String(std::move(buffer)); + auto base64String = Common::Unicode::Utf16ToUtf8(std::move(winrtString)); + + auto sendArgs = dynamic::array(std::move(base64String), socketID); + instance->callJSFunction("WebSocketModule", "sendBinary", std::move(sendArgs)); + } } ), @@ -97,6 +119,8 @@ std::vector BlobModule::getMethods() { [this](dynamic args) // blobId: string { auto blobId = jsArgAsString(args, 0); + + IWebSocketModuleContentHandler::GetInstance()->RemoveMessage(std::move(blobId)); } ) }; @@ -107,6 +131,44 @@ std::vector BlobModule::getMethods() { #pragma region IWebSocketModuleContentHandler overrides +void BlobWebSocketModuleContentHandler::Register(int64_t socketID) noexcept /*override*/ +{ + lock_guard lock{m_socketIDsMutex}; + m_socketIDs.insert(socketID); +} + +void BlobWebSocketModuleContentHandler::Unregister(int64_t socketID) noexcept /*override*/ +{ + lock_guard lock{m_socketIDsMutex}; + if (m_socketIDs.find(socketID) != m_socketIDs.end()) + m_socketIDs.erase(socketID); +} + +bool BlobWebSocketModuleContentHandler::IsRegistered(int64_t socketID) noexcept /*override*/ +{ + lock_guard lock{m_socketIDsMutex}; + return m_socketIDs.find(socketID) != m_socketIDs.end(); +} + +vector BlobWebSocketModuleContentHandler::ResolveMessage(string &&blobId, int64_t offset, int64_t size) noexcept +/*override*/ +{ + lock_guard lock{m_blobsMutex}; + + auto data = m_blobs.at(std::move(blobId)); + auto start = data.cbegin() + offset; + auto end = start + size; + + return vector(start, end); +} + +void BlobWebSocketModuleContentHandler::RemoveMessage(string&& blobId) noexcept +{ + lock_guard lock{m_blobsMutex}; + + m_blobs.erase(std::move(blobId)); +} + void BlobWebSocketModuleContentHandler::ProcessMessage(string&& message, dynamic& params) /*override*/ { params["data"] = std::move(message); diff --git a/vnext/Desktop/Modules/BlobModule.h b/vnext/Desktop/Modules/BlobModule.h index e57e1ad4245..1126dfb7034 100644 --- a/vnext/Desktop/Modules/BlobModule.h +++ b/vnext/Desktop/Modules/BlobModule.h @@ -9,17 +9,15 @@ #include // Standard Library -#include #include #include +#include +#include #include namespace Microsoft::React { class BlobModule : public facebook::xplat::module::CxxModule { - std::unordered_map> m_blobs; - std::mutex m_blobsMutex; - public: enum MethodId { AddNetworkingHandler = 0, @@ -58,11 +56,23 @@ class BlobWebSocketModuleContentHandler : public IWebSocketModuleContentHandler std::unordered_map> m_blobs; std::mutex m_blobsMutex; + std::unordered_set m_socketIDs; + std::mutex m_socketIDsMutex; public: #pragma region IWebSocketModuleContentHandler overrides + void Register(std::int64_t socketID) noexcept override; + + void Unregister(std::int64_t socketID) noexcept override; + + bool IsRegistered(std::int64_t socketID) noexcept override; + + std::vector ResolveMessage(std::string &&blobId, std::int64_t offset, std::int64_t size) noexcept override; + + void RemoveMessage(std::string &&blobId) noexcept override; + void ProcessMessage(std::string &&message, folly::dynamic ¶ms) override; void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) override; diff --git a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h index e087ed9bc7d..d3c7fc1f8ea 100644 --- a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h +++ b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h @@ -17,12 +17,21 @@ namespace Microsoft::React { /// struct IWebSocketModuleContentHandler { - //static void SetInstance(std::shared_ptr); - static std::shared_ptr GetInstance() noexcept; virtual ~IWebSocketModuleContentHandler() noexcept {} + virtual void Register(std::int64_t socketID) noexcept = 0; + + virtual void Unregister(std::int64_t socketID) noexcept = 0; + + virtual bool IsRegistered(std::int64_t socketID) noexcept = 0; + + virtual std::vector + ResolveMessage(std::string &&blobId, std::int64_t offset, std::int64_t size) noexcept = 0; + + virtual void RemoveMessage(std::string &&blobId) noexcept = 0; + virtual void ProcessMessage(std::string &&message, folly::dynamic ¶ms) = 0; virtual void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) = 0; diff --git a/vnext/Shared/Modules/WebSocketModule.h b/vnext/Shared/Modules/WebSocketModule.h index 25fa33493b7..6285ca442b9 100644 --- a/vnext/Shared/Modules/WebSocketModule.h +++ b/vnext/Shared/Modules/WebSocketModule.h @@ -55,7 +55,7 @@ class WebSocketModule : public facebook::xplat::module::CxxModule { /// Keeps IWebSocketResource instances identified by id. /// As defined in WebSocket.js. /// - std::map> m_webSockets; + std::map> m_webSockets; /// /// Generates IWebSocketResource instances, defaulting to IWebSocketResource::Make. From 8bc2d97124d3fe704979831cb535a8a9367e783d Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 23 Sep 2020 18:34:58 -0700 Subject: [PATCH 12/19] Implement createFromParts --- vnext/Desktop/Modules/BlobModule.cpp | 67 ++++++++++++++----- vnext/Desktop/Modules/BlobModule.h | 4 ++ vnext/Desktop/Modules/WebSocketModule.cpp | 35 +++++++++- .../Modules/IWebSocketModuleContentHandler.h | 4 +- 4 files changed, 91 insertions(+), 19 deletions(-) diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 60424813396..006f971cd49 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -19,9 +19,9 @@ using namespace facebook::xplat; using folly::dynamic; using std::lock_guard; using std::mutex; -using std::shared_ptr; using std::string; using std::vector; +using std::weak_ptr; using winrt::Windows::Foundation::GuidHelper; using winrt::Windows::Security::Cryptography::CryptographicBuffer; @@ -29,14 +29,14 @@ namespace { constexpr char moduleName[] = "BlobModule"; constexpr char blobURIScheme[] = "blob"; -//TODO: Check for memory leaks. -shared_ptr wsContentHandler; +weak_ptr s_contentHandler; } // namespace namespace Microsoft::React { BlobModule::BlobModule() noexcept { - wsContentHandler = std::make_shared(); + m_contentHandler = std::make_shared(); + s_contentHandler = m_contentHandler; } #pragma region CxxModule overrides @@ -58,6 +58,7 @@ std::vector BlobModule::getMethods() { "addNetworkingHandler", [this](dynamic args) { + //TODO: Implement } ), @@ -67,7 +68,7 @@ std::vector BlobModule::getMethods() { { auto id = jsArgAsInt(args, 0); - IWebSocketModuleContentHandler::GetInstance()->Register(id); + m_contentHandler->Register(id); } ), @@ -77,7 +78,7 @@ std::vector BlobModule::getMethods() { { auto id = jsArgAsInt(args, 0); - IWebSocketModuleContentHandler::GetInstance()->Unregister(id); + m_contentHandler->Unregister(id); } ), @@ -91,7 +92,7 @@ std::vector BlobModule::getMethods() { auto size = blob["size"].getInt(); auto socketID = jsArgAsInt(args, 1); - auto data = IWebSocketModuleContentHandler::GetInstance()->ResolveMessage(std::move(blobId), offset, size); + auto data = m_contentHandler->ResolveMessage(std::move(blobId), offset, size); if (auto instance = getInstance().lock()) { @@ -110,7 +111,38 @@ std::vector BlobModule::getMethods() { [this](dynamic args) { auto parts = jsArgAsArray(args, 0); // Array - auto withId = jsArgAsString(args, 1); + auto blobId = jsArgAsString(args, 1); + vector buffer{}; + + for(auto& part : parts) + { + auto type = part["type"]; + if (type == "blob") + { + auto blob = part["data"]; + auto blobId = blob["blobId"].asString(); + + auto bufferPart = m_contentHandler->ResolveMessage(blob["blobId"].asString(), blob["offset"].asInt(), blob["size"].asInt()); + buffer.reserve(buffer.size() + bufferPart.size()); + buffer.insert(buffer.end(), bufferPart.begin(), bufferPart.end()); + } + else if (type == "string") + { + auto data = part["data"].asString(); + auto bufferPart = vector(data.begin(), data.end()); + + buffer.reserve(buffer.size() + bufferPart.size()); + buffer.insert(buffer.end(), bufferPart.begin(), bufferPart.end()); + } + else// if (auto instance = getInstance().lock()) + { + //TODO: Error? + //instance->callJSFunction("RCTDeviceEventEmitter", "emit", dynamic::array("eventName", dynamic{})); + return; + } + + m_contentHandler->StoreMessage(std::move(buffer), std::move(blobId)); + } } ), @@ -120,7 +152,7 @@ std::vector BlobModule::getMethods() { { auto blobId = jsArgAsString(args, 0); - IWebSocketModuleContentHandler::GetInstance()->RemoveMessage(std::move(blobId)); + m_contentHandler->RemoveMessage(std::move(blobId)); } ) }; @@ -180,22 +212,25 @@ void BlobWebSocketModuleContentHandler::ProcessMessage(vector &&message blob("offset", 0); blob("size", message.size()); - // Equivalent to store() // substr(1, 36) strips curly braces from a GUID. string blobId = winrt::to_string(winrt::to_hstring(GuidHelper::CreateNewGuid())).substr(1, 36); - { - lock_guard lock{m_blobsMutex}; - m_blobs.insert_or_assign(blobId, std::move(message)); - } + StoreMessage(std::move(message), std::move(blobId)); params["data"] = std::move(blob); params["type"] = "blob"; } +void BlobWebSocketModuleContentHandler::StoreMessage(vector&& message, std::string&& blobId) noexcept +/*override*/ +{ + lock_guard lock{m_blobsMutex}; + m_blobs.insert_or_assign(std::move(blobId), std::move(message)); +} + #pragma endregion IWebSocketModuleContentHandler overrides -/*static*/ shared_ptr IWebSocketModuleContentHandler::GetInstance() noexcept { - return wsContentHandler; +/*static*/ weak_ptr IWebSocketModuleContentHandler::GetInstance() noexcept { + return s_contentHandler; } /*extern*/ std::unique_ptr CreateBlobModule() noexcept diff --git a/vnext/Desktop/Modules/BlobModule.h b/vnext/Desktop/Modules/BlobModule.h index 1126dfb7034..399c7781912 100644 --- a/vnext/Desktop/Modules/BlobModule.h +++ b/vnext/Desktop/Modules/BlobModule.h @@ -18,6 +18,8 @@ namespace Microsoft::React { class BlobModule : public facebook::xplat::module::CxxModule { + std::shared_ptr m_contentHandler; + public: enum MethodId { AddNetworkingHandler = 0, @@ -77,6 +79,8 @@ class BlobWebSocketModuleContentHandler : public IWebSocketModuleContentHandler void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) override; + void StoreMessage(std::vector &&message, std::string&& blobId) noexcept override; + #pragma endregion IWebSocketModuleContentHandler overrides }; diff --git a/vnext/Desktop/Modules/WebSocketModule.cpp b/vnext/Desktop/Modules/WebSocketModule.cpp index f7f53318568..275a62e30d8 100644 --- a/vnext/Desktop/Modules/WebSocketModule.cpp +++ b/vnext/Desktop/Modules/WebSocketModule.cpp @@ -5,10 +5,17 @@ #include +#include #include +#include "Unicode.h" + +// React Native #include #include -#include "Unicode.h" + +// Windows API +#include +#include using namespace facebook::xplat; using namespace folly; @@ -16,9 +23,11 @@ using namespace folly; using Microsoft::Common::Unicode::Utf16ToUtf8; using Microsoft::Common::Unicode::Utf8ToUtf16; +// Standard Library using std::shared_ptr; using std::string; using std::weak_ptr; +using winrt::Windows::Security::Cryptography::CryptographicBuffer; namespace { constexpr char moduleName[] = "WebSocketModule"; @@ -204,7 +213,29 @@ shared_ptr WebSocketModule::GetOrCreateWebSocket(int64_t id, if (!strongInstance) return; - auto args = dynamic::object("id", id)("data", message)("type", isBinary ? "binary" : "text"); + dynamic args = dynamic::object("id", id)("data", message)("type", isBinary ? "binary" : "text"); + + auto contentHandler = IWebSocketModuleContentHandler::GetInstance().lock(); + if (isBinary) + { + auto buffer = CryptographicBuffer::DecodeFromBase64String(Utf8ToUtf16(message)); + winrt::com_array arr; + CryptographicBuffer::CopyToByteArray(buffer, arr); + auto data = std::vector(arr.begin(), arr.end()); + + if (contentHandler) + contentHandler->ProcessMessage(std::move(data), args); + else + args["data"] = message; + } + else + { + if (contentHandler) + contentHandler->ProcessMessage(string{message}, args); + else + args["data"] = message; + } + this->SendEvent("websocketMessage", std::move(args)); }); ws->SetOnClose([this, id, weakInstance](IWebSocketResource::CloseCode code, const string& reason) diff --git a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h index d3c7fc1f8ea..212790318d0 100644 --- a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h +++ b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h @@ -17,7 +17,7 @@ namespace Microsoft::React { /// struct IWebSocketModuleContentHandler { - static std::shared_ptr GetInstance() noexcept; + static std::weak_ptr GetInstance() noexcept; virtual ~IWebSocketModuleContentHandler() noexcept {} @@ -35,6 +35,8 @@ struct IWebSocketModuleContentHandler { virtual void ProcessMessage(std::string &&message, folly::dynamic ¶ms) = 0; virtual void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) = 0; + + virtual void StoreMessage(std::vector &&message, std::string&& blobId) noexcept = 0; }; } // namespace Microsoft::React From 1d252aa76408b1cd681175ce2ad68f1848a59618 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 23 Sep 2020 19:36:16 -0700 Subject: [PATCH 13/19] Enable WebSocketBlobTest --- vnext/Desktop.DLL/react-native-win32.x64.def | 1 + vnext/Desktop.DLL/react-native-win32.x86.def | 1 + .../DesktopTestRunner.cpp | 3 ++- vnext/Desktop/HttpResource.cpp | 2 +- vnext/Desktop/Modules/BlobModule.cpp | 20 +++++++++---------- vnext/Desktop/Modules/BlobModule.h | 10 ++++------ vnext/Desktop/NativeModuleFactories.h | 8 ++++++-- .../Modules/IWebSocketModuleContentHandler.h | 3 +-- .../src/IntegrationTests/WebSocketBlobTest.js | 12 ++++++----- 9 files changed, 32 insertions(+), 28 deletions(-) diff --git a/vnext/Desktop.DLL/react-native-win32.x64.def b/vnext/Desktop.DLL/react-native-win32.x64.def index 39eccfb7d9e..b0e46164961 100644 --- a/vnext/Desktop.DLL/react-native-win32.x64.def +++ b/vnext/Desktop.DLL/react-native-win32.x64.def @@ -59,6 +59,7 @@ EXPORTS ??0WebSocketModule@React@Microsoft@@QEAA@XZ ??0NetworkingModule@React@Microsoft@@QEAA@XZ ?CreateAsyncStorageModule@react@facebook@@YA?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@PEB_W@Z +?CreateBlobModule@React@Microsoft@@YA?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@XZ YGAssert YGAssertWithConfig diff --git a/vnext/Desktop.DLL/react-native-win32.x86.def b/vnext/Desktop.DLL/react-native-win32.x86.def index e1d4f422493..220d65e0216 100644 --- a/vnext/Desktop.DLL/react-native-win32.x86.def +++ b/vnext/Desktop.DLL/react-native-win32.x86.def @@ -59,6 +59,7 @@ EXPORTS ?CreateAsyncStorageModule@react@facebook@@YG?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@PB_W@Z ?Make@IHttpResource@React@Microsoft@@SG?AV?$unique_ptr@UIHttpResource@React@Microsoft@@U?$default_delete@UIHttpResource@React@Microsoft@@@std@@@std@@XZ ??0NetworkingModule@React@Microsoft@@QAE@XZ +?CreateBlobModule@React@Microsoft@@YG?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@XZ _YGAlignToString@4 _YGAssert@8 diff --git a/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp b/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp index 42de7b57629..fa281260981 100644 --- a/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp +++ b/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp @@ -72,7 +72,8 @@ shared_ptr TestRunner::GetInstance( make_tuple( TestDeviceInfoModule::name, []() -> unique_ptr { return make_unique(); }, - nativeQueue)}; + nativeQueue), + make_tuple("BlobModule", []() -> unique_ptr { return CreateBlobModule(); }, nativeQueue)}; // <0> string // <1> CxxModule::Provider diff --git a/vnext/Desktop/HttpResource.cpp b/vnext/Desktop/HttpResource.cpp index ac687526cf4..2311ae9bbc3 100644 --- a/vnext/Desktop/HttpResource.cpp +++ b/vnext/Desktop/HttpResource.cpp @@ -35,7 +35,7 @@ void HttpResource::SendRequest( int64_t timeout, std::function &&callback) noexcept { // Enforce supported args - assert(responseType == "text" || responseType == "base64"); + assert(responseType == "text" || responseType == "base64" || "blob"); assert(!useIncrementalUpdates); // ISS:2306365 - Callback with the requestId diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 006f971cd49..39eed4ec46e 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -41,8 +41,7 @@ BlobModule::BlobModule() noexcept { #pragma region CxxModule overrides -string BlobModule::getName() -{ +string BlobModule::getName() { return moduleName; } @@ -182,26 +181,26 @@ bool BlobWebSocketModuleContentHandler::IsRegistered(int64_t socketID) noexcept return m_socketIDs.find(socketID) != m_socketIDs.end(); } -vector BlobWebSocketModuleContentHandler::ResolveMessage(string &&blobId, int64_t offset, int64_t size) noexcept +vector +BlobWebSocketModuleContentHandler::ResolveMessage(string &&blobId, int64_t offset, int64_t size) noexcept /*override*/ { lock_guard lock{m_blobsMutex}; auto data = m_blobs.at(std::move(blobId)); - auto start = data.cbegin() + offset; - auto end = start + size; + auto start = data.cbegin() + static_cast(offset); + auto end = start + static_cast(size); return vector(start, end); } -void BlobWebSocketModuleContentHandler::RemoveMessage(string&& blobId) noexcept -{ +void BlobWebSocketModuleContentHandler::RemoveMessage(string &&blobId) noexcept { lock_guard lock{m_blobsMutex}; m_blobs.erase(std::move(blobId)); } -void BlobWebSocketModuleContentHandler::ProcessMessage(string&& message, dynamic& params) /*override*/ +void BlobWebSocketModuleContentHandler::ProcessMessage(string &&message, dynamic ¶ms) /*override*/ { params["data"] = std::move(message); } @@ -220,7 +219,7 @@ void BlobWebSocketModuleContentHandler::ProcessMessage(vector &&message params["type"] = "blob"; } -void BlobWebSocketModuleContentHandler::StoreMessage(vector&& message, std::string&& blobId) noexcept +void BlobWebSocketModuleContentHandler::StoreMessage(vector &&message, std::string &&blobId) noexcept /*override*/ { lock_guard lock{m_blobsMutex}; @@ -233,8 +232,7 @@ void BlobWebSocketModuleContentHandler::StoreMessage(vector&& message, return s_contentHandler; } -/*extern*/ std::unique_ptr CreateBlobModule() noexcept -{ +/*extern*/ std::unique_ptr CreateBlobModule() noexcept { return std::make_unique(); } diff --git a/vnext/Desktop/Modules/BlobModule.h b/vnext/Desktop/Modules/BlobModule.h index 399c7781912..e7be5af46e6 100644 --- a/vnext/Desktop/Modules/BlobModule.h +++ b/vnext/Desktop/Modules/BlobModule.h @@ -55,14 +55,12 @@ class BlobModule : public facebook::xplat::module::CxxModule { }; class BlobWebSocketModuleContentHandler : public IWebSocketModuleContentHandler { - std::unordered_map> m_blobs; std::mutex m_blobsMutex; std::unordered_set m_socketIDs; std::mutex m_socketIDsMutex; -public: - + public: #pragma region IWebSocketModuleContentHandler overrides void Register(std::int64_t socketID) noexcept override; @@ -71,7 +69,8 @@ class BlobWebSocketModuleContentHandler : public IWebSocketModuleContentHandler bool IsRegistered(std::int64_t socketID) noexcept override; - std::vector ResolveMessage(std::string &&blobId, std::int64_t offset, std::int64_t size) noexcept override; + std::vector + ResolveMessage(std::string &&blobId, std::int64_t offset, std::int64_t size) noexcept override; void RemoveMessage(std::string &&blobId) noexcept override; @@ -79,10 +78,9 @@ class BlobWebSocketModuleContentHandler : public IWebSocketModuleContentHandler void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) override; - void StoreMessage(std::vector &&message, std::string&& blobId) noexcept override; + void StoreMessage(std::vector &&message, std::string &&blobId) noexcept override; #pragma endregion IWebSocketModuleContentHandler overrides - }; } // namespace Microsoft::React diff --git a/vnext/Desktop/NativeModuleFactories.h b/vnext/Desktop/NativeModuleFactories.h index 8e4a2e5118a..cb958afca7b 100644 --- a/vnext/Desktop/NativeModuleFactories.h +++ b/vnext/Desktop/NativeModuleFactories.h @@ -16,9 +16,13 @@ extern std::unique_ptr CreateAsyncStorageMod extern std::unique_ptr CreateTimingModule( const std::shared_ptr &nativeThread) noexcept; +} // namespace react +} // namespace facebook + +namespace Microsoft::React { + extern std::unique_ptr CreateWebSocketModule() noexcept; extern std::unique_ptr CreateBlobModule() noexcept; -} // namespace react -} // namespace facebook +} // namespace Microsoft::React diff --git a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h index 212790318d0..568aed6dda1 100644 --- a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h +++ b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h @@ -16,7 +16,6 @@ namespace Microsoft::React { /// See https://github.com/facebook/react-native/blob/v0.63.2/React/CoreModules/RCTWebSocketModule.h#L12 /// struct IWebSocketModuleContentHandler { - static std::weak_ptr GetInstance() noexcept; virtual ~IWebSocketModuleContentHandler() noexcept {} @@ -36,7 +35,7 @@ struct IWebSocketModuleContentHandler { virtual void ProcessMessage(std::vector &&message, folly::dynamic ¶ms) = 0; - virtual void StoreMessage(std::vector &&message, std::string&& blobId) noexcept = 0; + virtual void StoreMessage(std::vector &&message, std::string &&blobId) noexcept = 0; }; } // namespace Microsoft::React diff --git a/vnext/src/IntegrationTests/WebSocketBlobTest.js b/vnext/src/IntegrationTests/WebSocketBlobTest.js index e74af7d8659..6096190f544 100644 --- a/vnext/src/IntegrationTests/WebSocketBlobTest.js +++ b/vnext/src/IntegrationTests/WebSocketBlobTest.js @@ -12,7 +12,7 @@ const React = require('react'); const ReactNative = require('react-native'); -const {View} = ReactNative; +const {AppRegistry, View} = ReactNative; const {TestModule} = ReactNative.NativeModules; const DEFAULT_WS_URL = 'ws://localhost:5557/'; @@ -31,7 +31,7 @@ type State = { ... }; -class WebSocketBinaryTest extends React.Component<{}, State> { +class WebSocketBlobTest extends React.Component<{}, State> { state: State = { url: DEFAULT_WS_URL, fetchStatus: null, @@ -111,7 +111,7 @@ class WebSocketBinaryTest extends React.Component<{}, State> { console.warn(this.state.lastMessage?.size); console.warn('bat'); console.warn(this.state.testExpectedResponse.length); - + if ( this.state.lastMessage?.size !== this.state.testExpectedResponse.length @@ -168,6 +168,8 @@ class WebSocketBinaryTest extends React.Component<{}, State> { } } -WebSocketBinaryTest.displayName = 'WebSocketBinaryTest'; +WebSocketBlobTest.displayName = 'WebSocketBlobTest'; + +AppRegistry.registerComponent('WebSocketBlobTest', () => WebSocketBlobTest); -module.exports = WebSocketBinaryTest; +module.exports = WebSocketBlobTest; From e8833633964b1b3da8a7ab7535f9eca8fd9e4676 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 23 Sep 2020 19:55:39 -0700 Subject: [PATCH 14/19] yarn lint --- vnext/overrides.json | 11 ++++++- .../src/IntegrationTests/WebSocketBlobTest.js | 31 +++++++------------ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/vnext/overrides.json b/vnext/overrides.json index 4eee38febce..d51c6557856 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -10,7 +10,8 @@ "DeforkingPatches/README.md", "src/tsconfig.json", "**/*.windesktop.js", - "src/RNTester/js/examples-win/**" + "src/RNTester/js/examples-win/**", + "src/IntegrationTests/*Test.js" ], "overrides": [ { @@ -84,10 +85,18 @@ "type": "platform", "file": "src/IntegrationTests/LoggingTest.js" }, + { + "type": "platform", + "file": "src/IntegrationTests/websocket_integration_test_server_blob.js" + }, { "type": "platform", "file": "src/IntegrationTests/websocket_integration_test_server_binary.js" }, + { + "type": "platform", + "file": "src/IntegrationTests/WebSocketBlobTest.js" + }, { "type": "platform", "file": "src/IntegrationTests/WebSocketBinaryTest.js" diff --git a/vnext/src/IntegrationTests/WebSocketBlobTest.js b/vnext/src/IntegrationTests/WebSocketBlobTest.js index 6096190f544..94a184ed3a5 100644 --- a/vnext/src/IntegrationTests/WebSocketBlobTest.js +++ b/vnext/src/IntegrationTests/WebSocketBlobTest.js @@ -108,29 +108,22 @@ class WebSocketBlobTest extends React.Component<{}, State> { }; _receivedTestExpectedResponse = () => { - console.warn(this.state.lastMessage?.size); - console.warn('bat'); - console.warn(this.state.testExpectedResponse.length); - - if ( - this.state.lastMessage?.size !== - this.state.testExpectedResponse.length - ) { - return false; - } - -// for (var i = 0; i < expected.length; i++) { -// if (expected[i] !== result[i]) { -// return false; -// } -// } -// + if ( + this.state.lastMessage?.size !== this.state.testExpectedResponse.length + ) { + return false; + } + + //for (var i = 0; i < expected.length; i++) { + // if (expected[i] !== result[i]) { + // return false; + // } + //} + return true; }; componentDidMount() { - console.log('mounted') - console.warn('mounted') this.testConnect(); } From 8824b6e0adf7c40a5dad31628e3573bba6cf4596 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 23 Sep 2020 19:56:04 -0700 Subject: [PATCH 15/19] Change files --- ...tive-windows-2020-09-23-19-56-04-issues-3178-blob.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 change/react-native-windows-2020-09-23-19-56-04-issues-3178-blob.json diff --git a/change/react-native-windows-2020-09-23-19-56-04-issues-3178-blob.json b/change/react-native-windows-2020-09-23-19-56-04-issues-3178-blob.json new file mode 100644 index 00000000000..be04b1ad71a --- /dev/null +++ b/change/react-native-windows-2020-09-23-19-56-04-issues-3178-blob.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Implement BlobModule", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-09-24T02:56:04.067Z" +} From 359f9c92e0143b89d3d40efa1a2ad54c06871bf0 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 23 Sep 2020 20:04:45 -0700 Subject: [PATCH 16/19] Add TestRunner.UseWebDebugger runtime option. --- vnext/IntegrationTests/TestRunner.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vnext/IntegrationTests/TestRunner.cpp b/vnext/IntegrationTests/TestRunner.cpp index 30bd803cddb..f968cffdb76 100644 --- a/vnext/IntegrationTests/TestRunner.cpp +++ b/vnext/IntegrationTests/TestRunner.cpp @@ -6,10 +6,14 @@ #include #include -#include +#include #include "TestInstance.h" #include "TestModule.h" +// React Native +#include + +// Windows API #include #include @@ -69,7 +73,8 @@ TestResult TestRunner::RunTest(string &&bundlePath, string &&appName, NativeLogg // Note, further configuration should be done in each Windows variant's // TestRunner implementation. shared_ptr devSettings = make_shared(); - devSettings->useWebDebugger = true; // WebSocketJSExecutor can't register native log hooks.//TODO: Revert! + // WebSocketJSExecutor can't register native log hooks. Default to false. + devSettings->useWebDebugger = GetRuntimeOptionBool("TestRunner.UseWebDebugger"); devSettings->liveReloadCallback = []() {}; // Enables ChakraExecutor devSettings->errorCallback = [&result](string message) { result.Message = Microsoft::Common::Unicode::Utf8ToUtf16(message); From 02e3464ab3cbea4396782dfd7edb89b5ed1cc057 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 23 Sep 2020 22:21:37 -0700 Subject: [PATCH 17/19] Address feedback --- vnext/Desktop/HttpResource.cpp | 2 +- vnext/Desktop/Modules/BlobModule.cpp | 195 ++++++++---------- vnext/Desktop/Modules/BlobModule.h | 6 +- .../Modules/IWebSocketModuleContentHandler.h | 2 +- 4 files changed, 91 insertions(+), 114 deletions(-) diff --git a/vnext/Desktop/HttpResource.cpp b/vnext/Desktop/HttpResource.cpp index 2311ae9bbc3..2e8a67ec351 100644 --- a/vnext/Desktop/HttpResource.cpp +++ b/vnext/Desktop/HttpResource.cpp @@ -35,7 +35,7 @@ void HttpResource::SendRequest( int64_t timeout, std::function &&callback) noexcept { // Enforce supported args - assert(responseType == "text" || responseType == "base64" || "blob"); + assert(responseType == "text" || responseType == "base64" || responseType == "blob"); assert(!useIncrementalUpdates); // ISS:2306365 - Callback with the requestId diff --git a/vnext/Desktop/Modules/BlobModule.cpp b/vnext/Desktop/Modules/BlobModule.cpp index 39eed4ec46e..50957320e44 100644 --- a/vnext/Desktop/Modules/BlobModule.cpp +++ b/vnext/Desktop/Modules/BlobModule.cpp @@ -35,8 +35,10 @@ weak_ptr s_contentHandler; namespace Microsoft::React { BlobModule::BlobModule() noexcept { - m_contentHandler = std::make_shared(); - s_contentHandler = m_contentHandler; + if (!m_contentHandler) { + m_contentHandler = std::make_shared(); + s_contentHandler = m_contentHandler; + } } #pragma region CxxModule overrides @@ -49,114 +51,89 @@ std::map BlobModule::getConstants() { return {{"BLOB_URI_SCHEME", blobURIScheme}, {"BLOB_URI_HOST", {}}}; } -// clang-format off std::vector BlobModule::getMethods() { - return - { - Method( - "addNetworkingHandler", - [this](dynamic args) - { - //TODO: Implement - } - ), - - Method( - "addWebSocketHandler", - [this](dynamic args) - { - auto id = jsArgAsInt(args, 0); - - m_contentHandler->Register(id); - } - ), - - Method( - "removeWebSocketHandler", - [this](dynamic args) - { - auto id = jsArgAsInt(args, 0); - - m_contentHandler->Unregister(id); - } - ), - - Method( - "sendOverSocket", - [this](dynamic args) - { - auto blob = jsArgAsObject(args, 0); - auto blobId = blob["blobId"].getString(); - auto offset = blob["offset"].getInt(); - auto size = blob["size"].getInt(); - auto socketID = jsArgAsInt(args, 1); - - auto data = m_contentHandler->ResolveMessage(std::move(blobId), offset, size); - - if (auto instance = getInstance().lock()) - { - auto buffer = CryptographicBuffer::CreateFromByteArray(data); - auto winrtString = CryptographicBuffer::EncodeToBase64String(std::move(buffer)); - auto base64String = Common::Unicode::Utf16ToUtf8(std::move(winrtString)); - - auto sendArgs = dynamic::array(std::move(base64String), socketID); - instance->callJSFunction("WebSocketModule", "sendBinary", std::move(sendArgs)); - } - } - ), - - Method( - "createFromParts", - [this](dynamic args) - { - auto parts = jsArgAsArray(args, 0); // Array - auto blobId = jsArgAsString(args, 1); - vector buffer{}; - - for(auto& part : parts) - { - auto type = part["type"]; - if (type == "blob") - { - auto blob = part["data"]; - auto blobId = blob["blobId"].asString(); - - auto bufferPart = m_contentHandler->ResolveMessage(blob["blobId"].asString(), blob["offset"].asInt(), blob["size"].asInt()); - buffer.reserve(buffer.size() + bufferPart.size()); - buffer.insert(buffer.end(), bufferPart.begin(), bufferPart.end()); - } - else if (type == "string") - { - auto data = part["data"].asString(); - auto bufferPart = vector(data.begin(), data.end()); - - buffer.reserve(buffer.size() + bufferPart.size()); - buffer.insert(buffer.end(), bufferPart.begin(), bufferPart.end()); - } - else// if (auto instance = getInstance().lock()) - { - //TODO: Error? - //instance->callJSFunction("RCTDeviceEventEmitter", "emit", dynamic::array("eventName", dynamic{})); - return; - } - - m_contentHandler->StoreMessage(std::move(buffer), std::move(blobId)); - } - } - ), - - Method( - "release", - [this](dynamic args) // blobId: string - { - auto blobId = jsArgAsString(args, 0); - - m_contentHandler->RemoveMessage(std::move(blobId)); - } - ) - }; + return {Method( + "addNetworkingHandler", + [this](dynamic args) { + // TODO: Implement #6081 + }), + + Method( + "addWebSocketHandler", + [this](dynamic args) { + auto id = jsArgAsInt(args, 0); + + m_contentHandler->Register(id); + }), + + Method( + "removeWebSocketHandler", + [this](dynamic args) { + auto id = jsArgAsInt(args, 0); + + m_contentHandler->Unregister(id); + }), + + Method( + "sendOverSocket", + [this](dynamic args) { + auto blob = jsArgAsObject(args, 0); + auto blobId = blob["blobId"].getString(); + auto offset = blob["offset"].getInt(); + auto size = blob["size"].getInt(); + auto socketID = jsArgAsInt(args, 1); + + auto data = m_contentHandler->ResolveMessage(std::move(blobId), offset, size); + + if (auto instance = getInstance().lock()) { + auto buffer = CryptographicBuffer::CreateFromByteArray(data); + auto winrtString = CryptographicBuffer::EncodeToBase64String(std::move(buffer)); + auto base64String = Common::Unicode::Utf16ToUtf8(std::move(winrtString)); + + auto sendArgs = dynamic::array(std::move(base64String), socketID); + instance->callJSFunction("WebSocketModule", "sendBinary", std::move(sendArgs)); + } + }), + + Method( + "createFromParts", + [this](dynamic args) { + auto parts = jsArgAsArray(args, 0); // Array + auto blobId = jsArgAsString(args, 1); + vector buffer{}; + + for (const auto &part : parts) { + auto type = part["type"]; + if (type == "blob") { + auto blob = part["data"]; + auto bufferPart = m_contentHandler->ResolveMessage( + blob["blobId"].asString(), blob["offset"].asInt(), blob["size"].asInt()); + buffer.reserve(buffer.size() + bufferPart.size()); + buffer.insert(buffer.end(), bufferPart.begin(), bufferPart.end()); + } else if (type == "string") { + auto data = part["data"].asString(); + auto bufferPart = vector(data.begin(), data.end()); + + buffer.reserve(buffer.size() + bufferPart.size()); + buffer.insert(buffer.end(), bufferPart.begin(), bufferPart.end()); + } else { + // TODO: Send error message to instance? + return; + } + + m_contentHandler->StoreMessage(std::move(buffer), std::move(blobId)); + } + }), + + Method( + "release", + [this](dynamic args) // blobId: string + { + auto blobId = jsArgAsString(args, 0); + + m_contentHandler->RemoveMessage(std::move(blobId)); + })}; } -// clang-format on #pragma endregion CxxModule overrides @@ -175,7 +152,7 @@ void BlobWebSocketModuleContentHandler::Unregister(int64_t socketID) noexcept /* m_socketIDs.erase(socketID); } -bool BlobWebSocketModuleContentHandler::IsRegistered(int64_t socketID) noexcept /*override*/ +const bool BlobWebSocketModuleContentHandler::IsRegistered(int64_t socketID) noexcept /*override*/ { lock_guard lock{m_socketIDsMutex}; return m_socketIDs.find(socketID) != m_socketIDs.end(); diff --git a/vnext/Desktop/Modules/BlobModule.h b/vnext/Desktop/Modules/BlobModule.h index e7be5af46e6..448e1cf7378 100644 --- a/vnext/Desktop/Modules/BlobModule.h +++ b/vnext/Desktop/Modules/BlobModule.h @@ -21,7 +21,7 @@ class BlobModule : public facebook::xplat::module::CxxModule { std::shared_ptr m_contentHandler; public: - enum MethodId { + enum class MethodId { AddNetworkingHandler = 0, AddWebSocketHandler = 1, RemoveWebSocketHandler = 2, @@ -54,7 +54,7 @@ class BlobModule : public facebook::xplat::module::CxxModule { #pragma endregion CxxModule overrides }; -class BlobWebSocketModuleContentHandler : public IWebSocketModuleContentHandler { +class BlobWebSocketModuleContentHandler final : public IWebSocketModuleContentHandler { std::unordered_map> m_blobs; std::mutex m_blobsMutex; std::unordered_set m_socketIDs; @@ -67,7 +67,7 @@ class BlobWebSocketModuleContentHandler : public IWebSocketModuleContentHandler void Unregister(std::int64_t socketID) noexcept override; - bool IsRegistered(std::int64_t socketID) noexcept override; + const bool IsRegistered(std::int64_t socketID) noexcept override; std::vector ResolveMessage(std::string &&blobId, std::int64_t offset, std::int64_t size) noexcept override; diff --git a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h index 568aed6dda1..cdb4592acef 100644 --- a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h +++ b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h @@ -24,7 +24,7 @@ struct IWebSocketModuleContentHandler { virtual void Unregister(std::int64_t socketID) noexcept = 0; - virtual bool IsRegistered(std::int64_t socketID) noexcept = 0; + virtual const bool IsRegistered(std::int64_t socketID) noexcept = 0; virtual std::vector ResolveMessage(std::string &&blobId, std::int64_t offset, std::int64_t size) noexcept = 0; From 2f64d171f1d127dd32e8062bd847216b476f001a Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 23 Sep 2020 23:23:44 -0700 Subject: [PATCH 18/19] Remove Executors filter --- vnext/Desktop/React.Windows.Desktop.vcxproj.filters | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters index 10ea6adcc36..94b742fce5c 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters @@ -10,9 +10,6 @@ {63472e53-239b-4816-af09-ed2789b8fa95} - - {57577307-7ee7-4a55-8d17-c142b3f6f9cb} - {ae049213-3bdf-4cbe-83f4-39625a414539} @@ -22,9 +19,6 @@ {13b0d9c2-9c50-4245-b23f-e120dc6442cf} - - {fe8806a2-f72f-4dc3-9996-3a0ee66101ef} - {56988676-220f-43a7-b8b1-8ecc51158498} From a0f5272d2140084daa29d2175927aacb782c5ad0 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Thu, 24 Sep 2020 15:06:56 -0700 Subject: [PATCH 19/19] Don't exclude RN-provided test files --- vnext/overrides.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vnext/overrides.json b/vnext/overrides.json index d51c6557856..39769d0027f 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -11,7 +11,9 @@ "src/tsconfig.json", "**/*.windesktop.js", "src/RNTester/js/examples-win/**", - "src/IntegrationTests/*Test.js" + "src/IntegrationTests/WebSocketBlobTest.js", + "src/IntegrationTests/websocket_integration_test_server_binary.js", + "src/IntegrationTests/websocket_integration_test_server_blob.js" ], "overrides": [ {