Skip to content

Commit

Permalink
feat(windows): Support headers and cookies in source prop (#2897)
Browse files Browse the repository at this point in the history
* [windows] Support headers and cookies in source prop

* minor fixes
  • Loading branch information
vahagnni committed Jun 1, 2023
1 parent 75e7801 commit 1851ead
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 24 deletions.
1 change: 1 addition & 0 deletions windows/ReactNativeWebView/ReactWebView.idl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace ReactNativeWebView{
runtimeclass ReactWebView2 : Windows.UI.Xaml.Controls.ContentPresenter{
ReactWebView2(Microsoft.ReactNative.IReactContext context);
void NavigateToHtml(String html);
void NavigateWithHeaders(String uri, Windows.Foundation.Collections.IMapView<String, String> headers);
Boolean MessagingEnabled;
};
#endif
Expand Down
161 changes: 142 additions & 19 deletions windows/ReactNativeWebView/ReactWebView2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
#include "ReactWebView2.g.cpp"
#include <winrt/Windows.Foundation.Metadata.h>
#include <optional>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cctype>


namespace winrt {
using namespace Microsoft::ReactNative;
Expand All @@ -22,47 +27,106 @@ namespace winrt {
using namespace Windows::UI::Popups;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::Storage::Streams;
} // namespace winrt

namespace winrt::ReactNativeWebView::implementation {
namespace helpers {
std::string trimString(const std::string& str) {
std::string trimmedString = str;

// Trim from start
trimmedString.erase(0, trimmedString.find_first_not_of(" \t\n\r\f\v"));

// Trim from end
trimmedString.erase(trimmedString.find_last_not_of(" \t\n\r\f\v") + 1);

return trimmedString;
}
std::vector<std::string> splitString(
const std::string& str,
const std::string& delim) {
std::vector<std::string> tokens;
auto startPos = 0;
auto endPos = str.find(delim);

while (endPos != std::string::npos) {
auto token = str.substr(startPos, endPos - startPos);
tokens.push_back(trimString(token));

startPos = endPos + delim.length();
endPos = str.find(delim, startPos);
}

auto lastToken = str.substr(startPos);
tokens.push_back(trimString(lastToken));

return tokens;
}

std::map<std::string, std::string> parseSetCookieHeader(
const std::string& setCookieHeader) {
std::map<std::string, std::string> cookie;

// Split the header into individual cookie strings
auto cookieStrings = splitString(setCookieHeader, ";");

// Extract the cookie name and value from the first string
auto nameValuePair = splitString(cookieStrings[0], "=");
cookie["Name"] = trimString(nameValuePair[0]);
cookie["Value"] = trimString(nameValuePair[1]);

// Extract the attributes from the remaining strings
for (std::size_t i = 1; i < cookieStrings.size(); ++i) {
auto attributeValuePair = splitString(cookieStrings[i], "=");
auto attributeName = attributeValuePair[0];
auto attributeValue =
attributeValuePair.size() > 1 ? attributeValuePair[1] : "";
cookie[attributeName] = trimString(attributeValue);
}

return cookie;
}
} // namespace HP


ReactWebView2::ReactWebView2(winrt::IReactContext const& reactContext) : m_reactContext(reactContext) {
m_webView = winrt::WebView2();
this->Content(m_webView);
RegisterEvents();
}

ReactWebView2::~ReactWebView2(){}
ReactWebView2::~ReactWebView2() {}

void ReactWebView2::RegisterEvents() {
m_navigationStartingRevoker = m_webView.NavigationStarting(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) {
if (auto self = ref.get()) {
self->OnNavigationStarting(sender, args);
}
});
if (auto self = ref.get()) {
self->OnNavigationStarting(sender, args);
}

});

m_navigationCompletedRevoker = m_webView.NavigationCompleted(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) {
if (auto self = ref.get()) {
self->OnNavigationCompleted(sender, args);
}
});
if (auto self = ref.get()) {
self->OnNavigationCompleted(sender, args);
}
});

m_CoreWebView2InitializedRevoker = m_webView.CoreWebView2Initialized(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args){
if (auto self = ref.get()) {
self->OnCoreWebView2Initialized(sender, args);
}
});
}
}

bool ReactWebView2::Is17763OrHigher() {
static std::optional<bool> hasUniversalAPIContract_v7;

if (!hasUniversalAPIContract_v7.has_value()) {
hasUniversalAPIContract_v7 = winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 7);
hasUniversalAPIContract_v7 = winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 7);
}
return hasUniversalAPIContract_v7.value();
}
Expand All @@ -72,11 +136,11 @@ namespace winrt::ReactNativeWebView::implementation {
WriteProperty(eventDataWriter, L"canGoBack", sender.CanGoBack());
WriteProperty(eventDataWriter, L"canGoForward", sender.CanGoForward());
if (Is17763OrHigher()) {
WriteProperty(eventDataWriter, L"loading", !sender.IsLoaded());
WriteProperty(eventDataWriter, L"loading", !sender.IsLoaded());
}
WriteProperty(eventDataWriter, L"target", tag);
if (auto uri = sender.Source()) {
WriteProperty(eventDataWriter, L"url", uri.AbsoluteCanonicalUri());
WriteProperty(eventDataWriter, L"url", uri.AbsoluteCanonicalUri());
}
}

Expand All @@ -90,9 +154,9 @@ namespace winrt::ReactNativeWebView::implementation {
eventDataWriter.WriteObjectEnd();
});


if (m_messagingEnabled) {
m_messageToken = webView.WebMessageReceived([this](auto const& /* sender */ , winrt::CoreWebView2WebMessageReceivedEventArgs const& messageArgs)
m_messageToken = webView.WebMessageReceived([this](auto const& /* sender */, winrt::CoreWebView2WebMessageReceivedEventArgs const& messageArgs)
{
try {
auto message = messageArgs.TryGetWebMessageAsString();
Expand Down Expand Up @@ -121,18 +185,48 @@ namespace winrt::ReactNativeWebView::implementation {
});

if (m_messagingEnabled) {
winrt::hstring message = LR"(window.alert = function (msg) {window.chrome.webview.postMessage(`{"type":"__alert","message":"${msg}"}`)};
winrt::hstring message = LR"(window.alert = function (msg) {window.chrome.webview.postMessage(`{"type":"__alert","message":"${msg}"}`)};
window.ReactNativeWebView = {postMessage: function (data) {window.chrome.webview.postMessage(String(data))}};)";
webView.ExecuteScriptAsync(message);
}
}

void ReactWebView2::OnCoreWebView2Initialized(winrt::Microsoft::UI::Xaml::Controls::WebView2 const& sender, winrt::Microsoft::UI::Xaml::Controls::CoreWebView2InitializedEventArgs const& /* args */) {
assert(sender.CoreWebView2());

if (m_navigateToHtml != L"") {
m_webView.NavigateToString(m_navigateToHtml);
m_navigateToHtml = L"";
}
else if (m_navigationWithHeaders.has_value()) {
auto headers = m_navigationWithHeaders.value().second;

auto stream = InMemoryRandomAccessStream();

// construct headers string
winrt::hstring headers_str;
for (auto const& header : headers) {
if (header.Key() == L"cookie" || header.Key() == L"Cookie") {
WriteCookiesToWebView2(header.Value());
}
else {
headers_str = headers_str + header.Key() + L": " +
header.Value() + L"\r\n";
}

}

auto request =
m_webView.CoreWebView2().Environment().CreateWebResourceRequest(
m_navigationWithHeaders.value().first,
L"GET",
stream,
headers_str
);

m_webView.CoreWebView2().NavigateWithWebResourceRequest(request);
m_navigationWithHeaders.reset();
}
}

void ReactWebView2::HandleMessageFromJS(winrt::hstring const& message) {
Expand Down Expand Up @@ -161,11 +255,11 @@ namespace winrt::ReactNativeWebView::implementation {
});
}

void ReactWebView2::MessagingEnabled(bool enabled) noexcept{
void ReactWebView2::MessagingEnabled(bool enabled) noexcept {
m_messagingEnabled = enabled;
}

bool ReactWebView2::MessagingEnabled() const noexcept{
bool ReactWebView2::MessagingEnabled() const noexcept {
return m_messagingEnabled;
}

Expand All @@ -180,6 +274,35 @@ namespace winrt::ReactNativeWebView::implementation {
}
}

void ReactWebView2::NavigateWithHeaders(winrt::hstring uri, IMapView<winrt::hstring, winrt::hstring> headers) {
m_navigationWithHeaders = std::make_pair(uri, headers);
m_webView.EnsureCoreWebView2Async();
}

void ReactWebView2::WriteCookiesToWebView2(winrt::hstring cookies) {
// Persisting cookies passed from JS
// Cookies are separated by ;, and adheres to the Set-Cookie HTTP header format of RFC-6265.

auto cm = m_webView.CoreWebView2().CookieManager();
auto cookies_list =
helpers::splitString(winrt::to_string(cookies), ";,");
for (const auto& cookie_str : cookies_list) {
auto cookieData = helpers::parseSetCookieHeader(helpers::trimString(cookie_str));

if (!cookieData.count("Name") || !cookieData.count("Value")) {
continue;
}
auto cookie = cm.CreateCookie(
winrt::to_hstring(cookieData["Name"]),
winrt::to_hstring(cookieData["Value"]),
cookieData.count("Domain")
? winrt::to_hstring(cookieData["Domain"])
: L"",
cookieData.count("Path")
? winrt::to_hstring(cookieData["Path"]) : L"");
cm.AddOrUpdateCookie(cookie);
}
}
} // namespace winrt::ReactNativeWebView::implementation

#endif // HAS_WEBVIEW2
3 changes: 3 additions & 0 deletions windows/ReactNativeWebView/ReactWebView2.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ namespace winrt::ReactNativeWebView::implementation {
void MessagingEnabled(bool enabled) noexcept;
bool MessagingEnabled() const noexcept;
void NavigateToHtml(winrt::hstring html);
void NavigateWithHeaders(winrt::hstring uri, IMapView<winrt::hstring, winrt::hstring> headers);
~ReactWebView2();

private:
winrt::hstring m_navigateToHtml = L"";
std::optional<std::pair<winrt::hstring, IMapView<winrt::hstring, winrt::hstring>>> m_navigationWithHeaders;
bool m_messagingEnabled{ true };

winrt::Microsoft::UI::Xaml::Controls::WebView2 m_webView{ nullptr };
Expand All @@ -35,6 +37,7 @@ namespace winrt::ReactNativeWebView::implementation {
winrt::Microsoft::UI::Xaml::Controls::WebView2::CoreWebView2Initialized_revoker m_CoreWebView2InitializedRevoker{};
void HandleMessageFromJS(winrt::hstring const& message);
void RegisterEvents();
void WriteCookiesToWebView2(winrt::hstring cookies);
void WriteWebViewNavigationEventArg(winrt::Microsoft::UI::Xaml::Controls::WebView2 const& sender, winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter);
void OnNavigationStarting(winrt::Microsoft::UI::Xaml::Controls::WebView2 const& sender, winrt::Microsoft::Web::WebView2::Core::CoreWebView2NavigationStartingEventArgs const& args);
void OnNavigationCompleted(winrt::Microsoft::UI::Xaml::Controls::WebView2 const& sender, winrt::Microsoft::Web::WebView2::Core::CoreWebView2NavigationCompletedEventArgs const& args);
Expand Down
34 changes: 29 additions & 5 deletions windows/ReactNativeWebView/ReactWebView2Manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ namespace winrt {
using namespace Microsoft::UI::Xaml::Controls;
using namespace Windows::Web::Http;
using namespace Windows::Web::Http::Headers;
using namespace Microsoft::Web::WebView2::Core;
using namespace Windows::Storage::Streams;
}

namespace winrt::ReactNativeWebView::implementation {
Expand All @@ -28,8 +30,8 @@ namespace winrt::ReactNativeWebView::implementation {
}

winrt::FrameworkElement ReactWebView2Manager::CreateView() noexcept {
auto view = winrt::ReactNativeWebView::ReactWebView2(m_reactContext);
return view;
auto view = winrt::ReactNativeWebView::ReactWebView2(m_reactContext);
return view;
}

// IViewManagerWithReactContext
Expand Down Expand Up @@ -79,7 +81,29 @@ namespace winrt::ReactNativeWebView::implementation {
auto bundleRootPath = winrt::to_string(ReactNativeHost().InstanceSettings().BundleRootPath());
uriString.replace(0, std::size(file), bundleRootPath.empty() ? "ms-appx-web:///Bundle/" : bundleRootPath);
}
webView.Source(winrt::Uri(to_hstring(uriString)));

if (uriString.find("ms-appdata://") == 0 || uriString.find("ms-appx-web://") == 0) {
webView.Source(winrt::Uri(to_hstring(uriString)));
}
else {
const auto hasHeaders = srcMap.find("headers") != srcMap.end();

if (hasHeaders) {
auto headers = winrt::single_threaded_map<winrt::hstring, winrt::hstring>();

for (auto const& header : srcMap.at("headers").AsObject()) {
auto const& headerKey = header.first;
auto const& headerValue = header.second;
headers.Insert(winrt::to_hstring(headerKey), winrt::to_hstring(headerValue.AsString()));
}

const auto reactWebView2 = view.as<ReactNativeWebView::ReactWebView2>();
reactWebView2.NavigateWithHeaders(to_hstring(uriString), headers.GetView());
}
else {
webView.Source(winrt::Uri(to_hstring(uriString)));
}
}
}
else if (srcMap.find("html") != srcMap.end()) {
auto htmlString = srcMap.at("html").AsString();
Expand All @@ -92,7 +116,7 @@ namespace winrt::ReactNativeWebView::implementation {
auto reactWebView2 = view.as<ReactNativeWebView::ReactWebView2>();
reactWebView2.MessagingEnabled(messagingEnabled);
}
}
}
}

// IViewManagerWithExportedEventTypeConstants
Expand Down Expand Up @@ -147,7 +171,7 @@ namespace winrt::ReactNativeWebView::implementation {
}
else if (commandId == L"injectJavaScript") {
webView.ExecuteScriptAsync(winrt::to_hstring(commandArgs[0].AsString()));
}
}
}

} // namespace winrt::ReactNativeWebView::implementation
Expand Down
1 change: 1 addition & 0 deletions windows/ReactNativeWebView/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#if HAS_WEBVIEW2
#include <winrt/Microsoft.Web.WebView2.Core.h>
#include <winrt/Windows.Storage.Streams.h>
#endif

0 comments on commit 1851ead

Please sign in to comment.