Coder Social home page Coder Social logo

mauihybridwebview's Introduction

.NET MAUI HybridWebView experiment

This repo has the experimental .NET MAUI HybridWebView control, which enables hosting arbitrary HTML/JS/CSS content in a WebView and enables communication between the code in the WebView (JavaScript) and the code that hosts the WebView (C#/.NET). For example, if you have an existing React JS application, you could host it in a cross-platform .NET MAUI native application, and build the back-end of the application using C# and .NET.

Example usage of the control:

<hwv:HybridWebView
    HybridAssetRoot="hybrid_root"
    MainFile="hybrid_app.html"
    RawMessageReceived="OnHybridWebViewRawMessageReceived" />

And here's how .NET code can call a JavaScript method:

var sum = await myHybridWebView.InvokeJsMethodAsync<int>("JsAddNumbers", 123, 456);

And the reverse, JavaScript code calling a .NET method:

HybridWebView.SendInvokeMessageToDotNet("CallMeFromScript", ["msg from js", 987]);

With JavaScript you can also asynchronously call a .NET method and get a result:

HybridWebView.SendInvokeMessageToDotNetAsync("CallMeFromScriptAsync", ["msg from js", 987])
	.then(result => console.log("Got result from .NET: " + result));

or

const result = await HybridWebView.SendInvokeMessageToDotNetAsync("CallMeFromScriptAsync", ["msg from js", 987]);

In addition to method invocation, sending "raw" messages is also supported.

What's in this repo?

Projects in this repo:

  • HybridWebView --> a cross-platform .NET MAUI control that can load static web assets (HTML, JS, CSS, etc.) and send messages between JavaScript and .NET
  • MauiCSharpInteropWebView --> a sample app that shows the basic functionality of sending messages and calling methods between JavaScript and .NET
  • MauiReactJSHybridApp --> a sample app that incorporates a pre-existing React-based todo application into a .NET MAUI cross-platform application

Discussions/questions/comments

See the main discussion topic here: dotnet/maui#12009

Or please log an issue in this repo for any other topics.

Getting Started

To get started, you'll need a .NET MAUI 8 project, then add the HybridWebView control, and add some web content to it.

Note: If you'd like to check out an already completed sample, go to https://github.com/Eilon/SampleMauiHybridWebViewProject

  1. Ensure you have Visual Studio 2022 with the .NET MAUI workload installed
  2. Create a .NET MAUI App project that targets .NET 8 (or use an existing one)
  3. Add a reference to the EJL.MauiHybridWebView package (NuGet package):
    1. Right-click on the Dependencies node in Solution Explorer and select Manage NuGet Packages
    2. Select the Browse tab
    3. Ensure the Include prerelease checkbox is checked
    4. In the search box enter ejl.mauihybridwebview
    5. Select the matching package, and click the Install button
  4. Register and add the HybridWebView control to a page in your app:
    1. In MauiProgram.cs add the line builder.Services.AddHybridWebView(); to register the HybridWebView components
    2. Open the MainPage.xaml file
    3. Delete the <ScrollView> control and all of its contents
    4. Add a xmlns:ejl="clr-namespace:HybridWebView;assembly=HybridWebView" declaration to the top-level <ContentPage ....> tag
    5. Add the markup <ejl:HybridWebView HybridAssetRoot="hybrid_root" RawMessageReceived="OnHybridWebViewRawMessageReceived" /> inside the <ContentPage> tag
    6. Open the MainPage.xaml.cs file
    7. Delete the count field, and the OnCounterClicked method, and replace it with the following code:
      private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebView.HybridWebViewRawMessageReceivedEventArgs e)
      {
          await Dispatcher.DispatchAsync(async () =>
          {
              await DisplayAlert("JavaScript message", e.Message, "OK");
          });
      }
  5. Now add some web content to the app:
    1. In Solution Explorer expand the Resources / Raw folder
    2. Create a new sub-folder called hybrid_root
    3. In the hybrid_root folder add a new file called index.html and replace its contents with:
      <!DOCTYPE html>
      
      <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
      <head>
          <meta charset="utf-8" />
          <title></title>
          <script src="_hwv/HybridWebView.js"></script>
          <script src="myapp.js"></script>
      </head>
      <body>
          <div>
              Your message: <input type="text" id="messageInput" />
          </div>
          <div>
              <button onclick="SendMessageToCSharp()">Send to C#!</button>
          </div>
      </body>
      </html>
    4. In the same folder add another file myapp.js with the following contents:
      function SendMessageToCSharp() {
          var message = document.getElementById('messageInput').value;
          HybridWebView.SendRawMessageToDotNet(message);
      }
  6. You can now run your .NET MAUI app with the HybridWebView control!
    1. You can run it on Windows, Android, iOS, or macOS
    2. When you launch the app, type text into the textbox and click the button to receive the message in C# code

Proxy URLs

The HybridWebView control can redirect URL requests to native code, and allow custom responses streams to be set. This allows scenarios such as dynamically generating content, loading content from compressed files like ZIP or SQLite, or loading content from the internet that doesn't support CORS.

To use this feature, handle the ProxyRequestReceived event in the HybridWebView control. When the event handler is called set the ResponseStream and optionally the ResponseContentType of the HybridWebViewProxyEventArgs object received in the OnProxyRequest method.

The HybridWebViewProxyEventArgs has the following properties:

Property Type Description
QueryParams IDictionary<string, string> The query string parameters of the request. Note that all values will be strings.
ResponseContentType string The content type of the response body. Default: "text/plain"
ResponseStream Stream The stream to use as the response body.
Url string The full URL that was requested.
myWebView.ProxyRequestReceived += async (args) =>
{
    //Use the query string parameters to determine what to do.
    if (args.QueryParams.TryGetValue("myParameter", out var myParameter))
	{
        //Add your logic to determine what to do. 
		if (myParameter == "myValue")
		{
            //Add logic to get your content (e.g. from a database, or generate it).
            //Can be anything that can be turned into a stream.

            //Set the response stream and optionally the content type.
			args.ResponseStream = new MemoryStream(Encoding.UTF8.GetBytes("This is the file content"));
			args.ResponseContentType = "text/plain";
		}
	}
};

In your web app, you can make requests to the proxy URL by either using relative paths like /proxy?myParameter=myValue or an absolute path by appending the relative path tot he pages origin location window.location.origin + '/proxy?myParameter=myValue'. Be sure to encode the query string parameters so that they are properly handled. Here are some ways to implement proxy URLs in your web app:

  1. Use proxy URLs with HTML tags.
    <img src="/proxy?myParameter=myValue" />
  2. Use proxy URLs in JavaScript.
    var request = window.location.origin + '/proxy?myParameter=' + encodeURIComponent('myValue');
    
    fetch(request)
        .then(response => response.text())
        .then(data => console.log(data));
  3. Use proxy URLs with other JavaScript libraries. Some libraries only allow you to pass in string URLs. If you want to create the response in C# (for example, to generate content, or load from a compressed file), you can use a proxy URL to allow you to fulfil the response in C#.

NOTE: Data from the webview can only be set in the proxy query string. POST body data is not supported as the native WebView in platforms do not support it.

How to run the source code in this repo

To run this app you need to have Visual Studio for Windows or Mac, including the .NET MAUI workload. Then clone this repo, open the solution, and run one of the sample projects.

MauiReactJSHybridApp React JS app

The MauiReactJSHybridApp sample contains portions of a pre-existing Todo App built using React JS.

The original React JS Todo app sample used here is based on this sample: https://github.com/mdn/todo-react. I created a fork at https://github.com/Eilon/todo-react that incorporates some small changes to call the .NET API from JavaScript to synchronize the Todo list between the two parts of the app.

To make changes to the fork and update the .NET MAUI app, here's what I do:

  1. Clone of the forked repo and open a terminal/console window in that folder
  2. Run yarn to ensure the JavaScript dependencies are installed
  3. Run set PUBLIC_URL=/ to establish the root of the app as / because that's the root of the .NET MAUI HybridWebView app
  4. Run npm run build to compile the app and produce a static version of it
    • If you get this error: Error: error:0308010C:digital envelope routines::unsupported
    • Then run set NODE_OPTIONS=--openssl-legacy-provider
    • And run this again: npm run build
  5. This will build the HTML/JS/CSS output into a new ./build folder
  6. Go to the MauiReactJSHybridApp's Resources/Raw/ReactTodoApp folder and delete all the existing files, and replace with the files from the previous step's ./build folder
  7. Then run the MauiReactJSHybridApp from Visual Studio

This project is licensed under the MIT License. However, portions of the incorporated source code may be subject to other licenses:

mauihybridwebview's People

Contributors

eilon avatar peterblazejewicz avatar rbrundritt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mauihybridwebview's Issues

MauiHybridWebView causes Android app to crash with R8 enabled - Any fix to make it R8 compatible? Bug project included to demonstrate.

Bug Description

Apps featuring MauiHybridWebView always run perfectly on the first app start (whether R8 is enabled or disabled).

However, if Android stops the app in the background (ie. OnStop() occurs) and restarts the app in the background, it then always crashes when R8 is enabled. When R8 is disabled, it restarts smoothly.

How to Trigger Android App Restart

To make Android stop and restart the app and demonstrate this:

  • As per this thread open Android Settings and in Developer Options, change Background Process Limit setting to 1 or zero.
  • Then when Maui App has started, switch to another 1-3 open apps (WhatsApp, Chrome, anything else) before coming back to the Maui app (which forces close and restart of it).

Using this method, every time, when the app is built with R8 in Release mode and Android tries to restart it, it crashes.

Bug Project

https://github.com/jonmdev/R8CrashBug

The Bug Project to demonstrate is just a default Maui app with MauiHybridWebView added. A simple index.html is copied and pasted in to the Resources folder. Main.xaml.cs is changed to:

namespace R8CrashBug {
    public partial class App : Application {
        public App() {
            
            ContentPage mainPage = new();
            this.MainPage = mainPage;

            AbsoluteLayout absRoot = new();
            mainPage.Content = absRoot; 

            HybridWebView.HybridWebView hybridWebView = new();
            hybridWebView.HybridAssetRoot = "hybridview";
            hybridWebView.MainFile = "index.html";
            hybridWebView.JSInvokeTarget = this;

            absRoot.Add(hybridWebView);
        }   
    }
}

No other changes are made.

Steps To Reproduce

  1. Open Developer Options in Android Phone and set Background Process Limit to 0-1 range.
  2. Build Bug Project in Release to Android device with R8 Enabled (as it is by default).
  3. Press ||| button on Android bottom tray and navigate to a few other open apps sequentially (this will trigger Android to run onStop() and stop the Maui App process in the background).
  4. Press ||| button on Android bottom tray and navigate back to the Maui App.
  5. With R8 enabled, it will always then briefly show the splash icon and crash.
  6. If you repeat the above steps with R8 disabled, no crash occurs - it will instead restart the app smoothly as expected.

I also note the plain default Maui app (without MauiHybridWebView) does not crash with R8 enabled or disabled. It only crashes with R8 enabled once MauiHybridWebView has been added. Thus this is specifically provoked by the addition of MauiHybridWebView.

Outlook

MauiHybridWebView is an absolute game changer for me which has solved some major problems I otherwise had no solution for. I absolutely love the project and appreciate your work on it. But we cannot have it crashing like this in real projects for rlease and R8 is necessary for modern Android release.

Any help or outlook on a fix would be very, very appreciated.

Thanks again @Eilon for your great work on the project and any solution you can offer.

HybridWebView ArgumentNullException when using HtmlWebViewSource instead of file

WebView supports setting its Source property to a HtmlWebViewSource object.

Expected behavior:
Since HybridWebView derives from WebView, it is expected that HybridWebView also supports this.

Actual behavior:
However, when doing that (and not specifying a html source file, the app crashes.

Stacktrace:

[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.ArgumentNullException: Value cannot be null. (Parameter 'path1')
[mono-rt]    at System.ArgumentNullException.Throw(String paramName)
[mono-rt]    at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
[mono-rt]    at System.IO.Path.Combine(String path1, String path2)
[mono-rt]    at HybridWebView.AndroidHybridWebViewClient.ShouldInterceptRequest(WebView view, IWebResourceRequest request)
[mono-rt]    at Android.Webkit.WebViewClient.n_ShouldInterceptRequest_Landroid_webkit_WebView_Landroid_webkit_WebResourceRequest_(IntPtr jnienv, IntPtr native__this, IntPtr native_view, IntPtr native_request) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Android.Webkit.WebViewClient.cs:line 801
[mono-rt]    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLL_L(_JniMarshal_PPLL_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:line 231

Enhancement Request: Support for Desktop Refresh without 404 Errors

Description

When using the HybridWebView experiment in a .NET MAUI application on a desktop platform, refreshing the page leads to a 404 error. This issue affects the usability and the user experience, especially during development and testing phases.

Steps to Reproduce

  1. Run a .NET MAUI application that uses HybridWebView on a desktop platform.
  2. Navigate to any content within the HybridWebView that loads from a local or proxied resource.
  3. Refresh the page.
  4. The application shows a 404 error page instead of reloading the current content.

Expected Behavior

The application should reload the current page/content within the HybridWebView without any errors, preserving the user's state and context where applicable.

Actual Behavior

The application fails to find the requested page after a refresh and displays a 404 error, indicating that the resource cannot be found.

Possible Solutions or Suggestions

  • Implement a mechanism to handle refresh actions within the HybridWebView control, potentially by caching the last loaded URL or state and attempting to reload it upon a refresh action.
  • Investigate if the issue is related to the way routing is handled within the .NET MAUI application or the underlying web view control and propose a solution to maintain routing context across refreshes.

Question

Is handling refresh actions on the desktop within the scope of the HybridWebView control, or should it be managed at the application level? Any guidance on best practices or recommendations would be greatly appreciated.

Environment

  • .NET MAUI version: [8.0.10]
  • Platform Target Frameworks:
    • Desktop: [Windows/MacOS version]
  • Relevant plugins or packages: EJL.MauiHybridWebView [1.0.0-preview6]

Additional Context

Add any other context or screenshots about the enhancement request here.

image

which sdk needed ?NETSDK1045

Hello , vs playing weird , it doesn't use the latest sdk ,

this error showing in my error list;

NETSDK1045

Error	NETSDK1045	The current .NET SDK does not support targeting .NET 7.0.  Either target .NET 6.0 or lower, or use a version of the .NET SDK that supports .NET 7.0.	HybridWebView	C:\Program Files\dotnet\sdk\6.0.400\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.TargetFrameworkInference.targets	144	

here's my info of dotnet --list-sdks

6.0.400 [C:\Program Files\dotnet\sdk]
7.0.302 [C:\Program Files\dotnet\sdk]
8.0.100-preview.4.23260.5 [C:\Program Files\dotnet\sdk]

VS doesn't show (install missing sdk -workloads message )

image

and here's the dotnet list package
output :

Project 'MauiCSharpInteropWebView' has the following package references
   [net7.0-android31.0]: No packages were found for this framework.
   [net7.0-ios15.4]: No packages were found for this framework.
   [net7.0-maccatalyst15.4]: No packages were found for this framework.
   [net7.0-windows10.0.19041]: No packages were found for this framework.
Project 'HybridWebView' has the following package references
   [net7.0]: No packages were found for this framework.
   [net7.0-android31.0]: No packages were found for this framework.
   [net7.0-ios15.4]: No packages were found for this framework.
   [net7.0-maccatalyst15.4]: No packages were found for this framework.
   [net7.0-windows10.0.19041]: No packages were found for this framework.
Project 'MauiReactJSHybridApp' has the following package references
   [net7.0-android31.0]: No packages were found for this framework.
   [net7.0-ios15.4]: No packages were found for this framework.
   [net7.0-maccatalyst15.4]: No packages were found for this framework.
   [net7.0-windows10.0.19041]: No packages were found for this framework.

BTW I enabled preview sdks in vs settings :

image

solutions I tried ;

dotnet workload update maui
updated .
tried to remove sdk& runtimes and reinstall them
-I tried fixing vs with vs tools (repair)
removing golbal.json file

I'm not comfortable with the <TargetFrameworks Condition> in the solution.

I did :
dotnet restore and
dotnet workload restore too no luck

Embed HybridWebView.js so apps can reference it more easily

Instead of having to add HybridWebView.js to every app, it would be nice if it was built-in and could be referenced easily.

The proposal is to embed the file and make it reachable via a tag like this:

    <script src="_hwv/HybridWebView.js"></script>

With _hwv being a prefix for known files.

Feature requests: Proxy requests

Update:

After some testing I found a solution that appears to address all the features I was originally looking for. Basically, if we have a way to proxy HTTP requests made by the browser to a native method/function in C# and allow setting the response stream/content type, a whole bunch of scenarios open up. Each browser has something similar already which this libraries leverages for the JavaScript communication, but in doing so, we lose this capability. Also this capability is buried at the platform level, so ideally this hybrid web view will allow for a single point of entry for this feature. Here are some example scenarios where this would be useful:

  • Create content in native code on the fly, like images, and pass it back to the browser, kind of like a local service.
  • Support the reverse of InvokeJsMethodAsync and be able to invoke C# code, receive a message and respond back to it.
  • Some mobile apps let the user download content for offline use. This is often stored as a compressed file, like a zip file. Using this feature, you can easily add code to access the zip file, check if a file is inside it, and if it is, pass it back to the browser.
  • When using web libraries that request files and we only have the ability to set the URL string. Most web map controls allow you to pass in a string URL with placeholders for custom map tile services, so without this proxy we would have to host the files as flat files in the app as assets or on a server. However, tile sets tend to consist of tens of thousands or more images and a common method to store them is within a sqlite database so that you only have to move around a single file. With this feature we could easily access a sqlite db, query it, and respond accordingly to the web request.
  • Currently this library is passing JSON strings around which may not be the most efficient data format in some scenarios. If I had a large array of numbers, passing that back as a binary stream would likely be faster.

Original post:

First off, really liking this library. I looked at a lot of others, but this looks to be the most complete. I've built some pretty advance apps with web browser/view interop in the past and tried out all the features in this library. Here are a couple of feature requests that I think would really help close the gap on a few scenarios:

  • Add support for custom extensions. Currently only .htm, .html, .js, .css appear to be supported. All other requests are set to text/plain. In my case I want to be able to load in binary files like .mp3 or .gltf, .glb
  • Add support for asynchronous responses on .NET methods invoked from JS. For clarity, I want to be able to call a .NET method from JS, and have the .NET code set a response that can be received on the JS side (round trip). Basically, the reverse of InvokeJsMethodAsync.
  • A similar scenario to the above I'd like to support that I think might just work if the above two items are added, is the ability to use this as a local REST service. In my case I'm using a web map control with a local tile imagery. I have thousands of map tiles, and store them in an SqlLite db to make it easier to manage and move around. I want to be able to create a proxy like method where I can run some custom code to generate my response, in this case check to see if the file exists and load it. A similar scenario might be involve wanting to dynamically create an image in native code in response to a request. In this case, the web map control only takes in string URL's, so I can't leverage the JS invoke methods. I was thinking looking for URL's like https://0.0.0.0/REST/ and having those trigger a suitable handler/callback might make sense.

I contemplated trying to add these things myself, but figured it would be better to start a discussion first, see if this is something you would want included, and what feedback others might have on the implementation. I'm fairly new to Maui development but have done a lot of Windows and Android development, so I should be able to at least figure out those two platforms.

Thanks!

Feature Request: Implement Observables for HybridWebView in .NET MAUI

I am currently working with .NET MAUI, specifically utilizing the HybridWebView component for a project. Through my experience, I've identified an area that could significantly enhance its functionality and usability for developers.

Feature Request:

I would like to propose the introduction of Observables for the HybridWebView component. This feature would enable developers to call HybridWebView.SendRawMessageToDotNet(message) and either wait for a response or subscribe to an Observable that would notify them when a response is received.

Use Case and Benefits:

  • Asynchronous Communication: This feature would facilitate asynchronous communication between the WebView and the .NET backend. It allows for more dynamic and responsive interactions within the application.

  • Improved Data Handling: By using Observables, developers can efficiently handle streams of data or events, especially useful in scenarios where the data is continuously updated or changes over time.

  • Enhanced Responsiveness: Applications can react to messages or data received in real-time, improving the user experience by providing immediate feedback or updates.

  • Simplification of Complex Flows: It can simplify the handling of complex communication flows between the web content and the .NET code, making the codebase cleaner and more maintainable.

  • Alignment with Reactive Programming Paradigms: As Reactive Programming is becoming increasingly popular, this feature would align well with current development trends and practices.

I believe the inclusion of this feature would greatly benefit the .NET MAUI community, offering enhanced flexibility and capabilities for those working with HybridWebView.

Thank you for considering this feature request. I am looking forward to any feedback or discussion regarding its potential implementation.

Typescript definitions

Just Trying to use it with a React Typescript SPA, but i don't get the javascript working in a typescript.

Already tried to make a global.d.ts, and make there the extends Operation to Window interface, but i get some Compiler Errors.

Can anybody help?

Release latest version to nuget

I see the latest version is published 5 months ago (targeting net7). I see you've made some improvements/commits. Would it be possible to release the latest as preview to nuget?

Saves us copying code :).

Future plans of HybridWebView ?

Hi, Elon!

At Ventla we decided to create a full-fledged Chat component in a React JS project and integrate it into our MAUI Mobile and Blazor Web projects using your amazing HybridWebView.

But we are concerned because this package stable version is not released yet. So we just want to know whether HybridWebView will become more mature in the near future? Or any other suggestion you want to give us is welcome.

Add support for ESProj files to incorporate JS/TS apps as project references

See sample app from @javiercn here: https://github.com/javiercn/EsprojPlusAspNetStaticWebAssets (and this ESProj file: https://github.com/javiercn/EsprojPlusAspNetStaticWebAssets/blob/main/LibraryProjectStaticWebAssets/LibraryProjectStaticWebAssets.esproj).

That app shows how to use an ESProj (ECMAScript Project) to include the output JS files as Static Web Assets in an ASP.NET web app. The same pattern could hopefully be used to reference a JS/TS app from a .NET MAUI app with HybridWebView. The .NET MAUI app could reference the ESProj and include those assets as MAUI Assets without any manual steps.

More info on ESProj: https://learn.microsoft.com/visualstudio/ide/reference/property-pages-javascript-esproj?view=vs-2022

Null Reference Exception when targeting .Net 8 Preview

Steps to reproduce

Clone the repo, and change the TargetFramework in the MauiCSharpInteropWebView to

<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>

When running the application, you'll get a null reference exception with no additional info (all threads are executing external code).

I am currently using Maui 8.0.0-preview.7.8842

Edit: It works on Windows, but does not work on Android. I have not tried iOS.

Other proxy code cleanup tasks

These tasks were originally noted in #43:

  • Consider renaming the proxy prefix to _proxy. Usually an underscore is used to differentiate 'built-in' features, which this would be.
  • Replace new GetKeyValuePairs() method with a built-in .NET API for parsing query string values
  • Consider changing the new JS method SendInvokeMessageToDotNetAsync to be another message type (we already have message type 0 (raw) and 1 (invoke method). It could then internally uses the new proxy mechanism, but perhaps not get mixed into the 'first-class' proxy feature? (This could make the actual proxy code a bit cleaner maybe?)

Note: #51 is specifically to address possible hangs due to sync-over-async usage of .Wait().

allow passing of object and null from HybridWebView.SendInvokeMessageToDotNet("CallMeFromScript", ["msg from js", 987]);

I am getting a response from payment gateway which I want to send to the C# for further processing like

 var respData = eval("(" + e.data + ")");
document.getElementById("monerisResponse").innerHTML = " Response Code: " +
    respData.responseCode + " Token: " + respData.dataKey + " Bind: " + respData.bin + " Error Message: " + respData.errorMessage;  
 document.getElementById("monerisFrame").style.display = 'none';

HybridWebView.SendInvokeMessageToDotNet("CallMeFromScript", [respData.dataKey, respData.bin]);`

But the exception is thrown when any parameter is empty. For example if respData.dataKey have value than respData.errorMessage is empty. But I want to send all the values and check which values are null in the C# code. Also respData.responseCode is throwing exception which I guess is because it is an int.

How to add Angular?

I'm new to MAUI but proficient in Angular. I need to access USB, Bluetooth, and NFC. I want to take my existing Angular project and add it to the resources/Raw folder. Can you guide me on how to get Angular running with MAUI. Do I need to run the ng server before I debug? I'll figure it out but maybe you have the answer already. Thanks.

reload inside the control

Hi,

can anyone knows how to implement a refresh functionality inside the control?

thank you in advance.

Feedback: Copy of stand-alone react app files doesn't load images

I have tried this as I am looking to host a react app within a standard MAUI XAML app.

Issue 1:
I cannot find a solution to reference the react project where the dist file will get copied to the Resources folder on build.

Issue 2:
I added a stand-alone react application project to the solution and copied the dist folder to Resources as per the example app.
The application runs but the images are not loaded.

In terms of the development workflow, I am not sure if the intention is to run the react app stand-alone and mock various aspects that might be handled by the MAUI app or when running the MAUI app everything gets built and run, or either? depending on the nature of the application, but, I would expect it to be seamless.

I appreciate this is an experiment, happy to do these things manually.

image

Visual Studio 17.8.3 / .net8 / Windows11

Query parameters

I'm currently building a PDF viewer using PDF.JS

Do to the URL having query string attached the PlatformOpenAppPackageFile doesnt ruturn a file -

Stream PlatformOpenAppPackageFile(string filename)
{
    filename = FileSystemUtils.NormalizePath(filename);

    try
    {
        return _handler.Context.Assets.Open(filename);
    }
    catch (Java.IO.FileNotFoundException)
    {
        return null;
    }
}

Example, viewer.html is the local PDF viewer, and file=*** is the PDF file to load -

  • (external pdf) - viewer.html?file=https://www.syncfusion.com/downloads/support/directtrac/general/pd/GIS_Succinctly1774404643.pdf
  • (embedded pdf) - viewer.html?file=Dissertation.pdf

Not quite sure how to get passed this issue.

Service workers cannot be registered

When trying to register a service worker, the WebView's dev tools show an "unknown error" message during registration. I have been digging into the error some, it appears that service worker registration either never makes an actual request, or that request goes through a different channel than normal requests--in any case the request does not go through the WebResourceRequested handler. This is, however, possible with a plain WebView directed to the same page.

Our use case is migrating an application that previously used Capacitor into MAUI, where we intend to use this package to provide interop for migrated and unmigrated components. I'll keep looking, but hoping somebody with more familiarity with the platform internals could figure it out in less time :)

The exact error is:
Uncaught (in promise) TypeError: Failed to register a ServiceWorker for scope ('https://0.0.0.0/') with script ('https://0.0.0.0/service-worker.js'): An unknown error occurred when fetching the script.

And attempting to run the call through the DevTools console results in the following error in the response in the Network tab:
image

This can be reproduced by installing this package onto a new Blazor MAUI project, with the following changes:

Resources/Raw/hybrid_app/index.html

<html>
  <head>
    <script>
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('service-worker.js');
      }
    </script>
  </head>
  <body>
  </body>
</html>

Resources/Raw/hybrid_app/service-worker.js

self.addEventListener('install', event => {});

self.addEventListener('activate', event => {});

self.addEventListener('fetch', event => {});

MainPage.xaml

<ContentPage ...>
  <xlj:HybridWebView HybridAssetRoot="hybrid_app" MainFile="index.html" EnableWebDevTools="True" />
</ContentPage>

Expose platform webview for initialization to enable customization

For example, in #27 (comment) this was mentioned:

Can I disable Android WebView TextZoom on this? I can set it by use blazorWebView in the Init event:

                private void blazorWebView_BlazorWebViewInitialized(object sender, Microsoft.AspNetCore.Components.WebView.BlazorWebViewInitializedEventArgs e)
                {
        #if ANDROID
                    e.WebView.Settings.TextZoom = 100;
                   e.WebView.Settings.SetSupportZoom(false);
        #endif
                }

but It seems private in your library =>private Android.Webkit.WebView PlatformWebView

HybridWebView not only access local SPA's but on the internet too!

From: dotnet/maui#12009 (reply in thread)

My use case is a SPA application in Vue (Static SPA) that we made hybrid for desktop(browser) and mobile.
This application is deployed on a simple public web server http://example.com/spa/index.html

We saw that it is very interesting because the end user can have the update automatically without having to update the app (just refreshing the webview).

It's similar to your React example but it's hosted on the web.

The need to have the interaction between C# and Javascript is necessary for "Native Menu" calling the spa menu when clicked, downloading files and other nice functions.

We already have an example of this in production with an app made in reactnative, but we want to migrate to MAUI and add more functionality to the app

I made a version that works with a fork from your project and some modifications. So its possible :)

keyboard does not shown on textboxes

Hi Eilon,

First of all i want to thank you for the
control you have created.
I am encountering an issue with the keyboard not appearing when interacting with text boxes using MauiHybridWebView.
I am using an external url not a static web page inside the project.

Please advice.

Thank you in advance.
Efthymios

Error when setting a property with a string longer 5000 chars

Hi Eilon ,

Im using the MauiHybridWebView in my MAUI App with the TinyMCE Editor, following along the blog from Adin Aronson (https://medium.com/@adinas/using-tinemce-in-your-net-maui-app-b869c9c22d8c).

Works like a charm - but only when I give it a html-string shorter than 5000 chars .

No error on windows, but on Android I get this error message:

Java.Lang.IndexOutOfBoundsException
Nachricht = setSpan (13587 ... 13587) ends beyond length 5000

Maybe that has nothing to do with your code but I still want to log this here. Perhaps you have an idea what goes wrong ?

Make it easy to enable browser dev tools

Add a new EnableWebDevTools Boolean property to HybridWebView to make it easy to enable browser dev tools.

You could then use it like this:

#if DEBUG
        myHybridWebView.EnableWebDevTools = true;
#endif

"Link" trimming mode on Android removing/deactivating/breaking certain functions in my HTML/Javascript HybridWebView project?

I first have to say HybridWebView is absolutely amazing. Being able to load in Javascript/HTML code and just run it in Maui is life changing. I hope this project will be maintained up to date as I am now dependent on it. It is letting me do things I have struggled to figure out for months. Thank you very much for doing this.

However, after hours of pain today, I tracked down a "bug" or at least unwanted behavior. I have a HTML file like this that is referenced by HybridWebView as its main page:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Test</title>
		<style>
			body { margin: 0; }
		</style>
		<script>
	        const module = {}; //use as empty module for attaching functions to 

                        //SIMPLE DEBUG TO SCREEN
			function rawDebugToScreen(textToShow){
				const debugText = document.getElementById("info");
				debugText.textContent += " | " + textToShow;
			}
			
			//ACCESSING AN INTERNAL MODULE FUNCTION TO DO THE SAME 
			function debugToScreen(textToShow){
				window.debugToScreen(textToShow);
				module.debugToScreen(textToShow);
			}
			
			function setRandomNewCubeColor(){
				window.setRandomNewCubeColor();
				module.setRandomNewCubeColor();
			}

		</script>

		<script src="_hwv/HybridWebView.js"></script>
		<script type="importmap">
		  {
		    "imports": {
		      "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
		      "three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
		    }
		  }
		</script>
		<script type="module" crossorigin src="/assets/index-CFzOgagS.js"></script>
		<link rel="stylesheet" crossorigin href="/assets/index-rX0QCEo-.css">
	</head>
	<body>
		
		<div id="info"></div>
	</body>
</html>

For reference, the module. and window. are just methods I am testing to run functions within a module from Maui.

This actually all works perfectly in Debug Android, Windows Debug/Release. But if I build for Android in Release with Trimming Granularity set to "Link", when I try to invoke the rawDebugToScreen or debugToScreen functions from Maui (simple tests to update some display text on screen to test interop) they both no longer work.

I can still have Enable Trimming on and even the R8 code shrinker with no problems.

But I must leave the Trimming Granularity field blank or these functions stop working when called from Maui.

I presume they are being pruned as the way they are called from Maui does not make it obvious they are actually used.

Is this an expected outcome? Is there any way to protect our functions in our Javascript/HTML from such pruning while still using this?

I must concede I don't know anything about the "Link" setting. I do need to keep my file sizes small as Play store only allows 100 MB. Having this on "Link" did help significantly as I am already up against the limit.

By contrast "CopyUsed" does not work at all for me - fails to build. So it would be good to use "Link" and have some simple way to ensure the functions aren't pruned/damaged.

I tried running the functions in index.html body like:

<script>
			rawDebugToScreen("");
			debugToScreen("");
</script>

But this doesn't save them.

Any thoughts or ideas are appreciated.

Edit: Just found out Android now allows up to 200 MB so maybe I don't have to even worry about this. Still I'm curious and would like to optimize what I can. Thanks.

Need to modify the HybridWebView configuration (BEFORE initializing) on iOS/MacCatalyst to fix known base WebView issue ... Any solution?

There is a problem with the WebView in iOS/MacCatalyst where one must define parameters only on initialization for the ability to play media inline (ie. not go full screen every time you play), autoplay, and configure other media playback parameters.

This is described here:

The two solutions at those locations both don't work for HybridWebView, at least not directly without Reflection for the following reasons:

1) Blazor Fix

The BlazorWebView fix is shown here: dotnet/maui#16013 (comment)

Where the user sets:
BlazorWebViewInitializing="HandleWebViewInitializing"

    private void HandleWebViewInitializing(object sender, BlazorWebViewInitializingEventArgs args)
    {
        // WebView needs the configuration updated during initialization in order for "playsinline" attributes 
        // to work in the HTML5 video player for iOS devices.
#if IOS || MACCATALYST
        args.Configuration.AllowsInlineMediaPlayback = true;
        args.Configuration.MediaPlaybackRequiresUserAction = false;
        args.Configuration.RequiresUserActionForMediaPlayback = false;
#endif
    }

However, we cannot use this directly as the only event we have in HybridWebView is only run AFTER initialization (which is too late - I tried - config must be set before creation of the WebView and can't be affected after):

            void initFunction(object sender, HybridWebViewInitializedEventArgs args) {
#if IOS || MACCATALYST

                //doesn't work as already initialized on this event
                args.WebView.Configuration.AllowsInlineMediaPlayback = true;
                args.WebView.Configuration.MediaPlaybackRequiresUserAction = false;
                args.WebView.Configuration.RequiresUserActionForMediaPlayback = false;
#endif
            }

            HybridWebView.HybridWebView webView = new();
            webView.HybridWebViewInitialized += initFunction; //does not work as only runs AFTER INITIALIZATION        

2) WebView Solution

The WebView solution is given here: dotnet/maui#4807 (comment) and here: dotnet/maui#11075 by @Redth:

There he suggests overriding the Microsoft.Maui.Handlers.WebViewHandler.PlatformViewFactory = (handler) => function. However, reviewing this in HybridWebView shows it is not so simple. The HybridWebView function is here:

However a similar approach doesn't work directly for numerous reasons:

  • Can't access: WebViewScriptMessageHandler, MessageReceived, or SchemeHandler this way.

Commenting out everything dependent on these breaks the HybridWebView.

3) Reflection Solution (WORKING)

I was just able to get a temporary fix using Reflection and redefining MessageReceived by modifying @Redth's solution approach:

#if IOS || MACCATALYST

            HybridWebView.HybridWebViewHandler.PlatformViewFactory = (handler) => {
                
                var config = new WKWebViewConfiguration();
                //====================================================
                //CUSTOM CONFIG
                config.AllowsAirPlayForMediaPlayback = true;
                config.AllowsInlineMediaPlayback = true;
                config.AllowsPictureInPictureMediaPlayback = true;
                config.MediaTypesRequiringUserActionForPlayback = WebKit.WKAudiovisualMediaTypes.None;

                //simple enough can just redundancy this one:
                Action<Uri, string> MessageReceivedAction = delegate (Uri uri, string message) { // to replace https://github.com/Eilon/MauiHybridWebView/blob/3ca801076a1e3fbe3b8922b2429524df20def6a4/HybridWebView/Platforms/iOS/HybridWebViewHandler.iOS.cs#L40
                    ((HybridWebView.HybridWebView)handler.VirtualView).OnMessageReceived(message);
                };

                //https://stackoverflow.com/a/39076814/10305478
                object CreatePrivateClassInstance(string typeName, object[] parameters) {
                    Type type = AppDomain.CurrentDomain.GetAssemblies().
                             SelectMany(assembly => assembly.GetTypes()).FirstOrDefault(t => t.Name == typeName);
                    return type.GetConstructors()[0].Invoke(parameters);
                }

                IWKScriptMessageHandler webViewScriptMessageHandler = (IWKScriptMessageHandler)CreatePrivateClassInstance("WebViewScriptMessageHandler", [MessageReceivedAction]);
                config.UserContentController.AddScriptMessageHandler(webViewScriptMessageHandler, "webwindowinterop");

                IWKUrlSchemeHandler? wKUrlSchemeHandler = (IWKUrlSchemeHandler?)CreatePrivateClassInstance("SchemeHandler", [handler as HybridWebViewHandler]);
                config.SetUrlSchemeHandler(wKUrlSchemeHandler, urlScheme: "app");
                //============================================


                //default programming built in
                //config.UserContentController.AddScriptMessageHandler(new WebViewScriptMessageHandler(MessageReceived), "webwindowinterop");
                //config.SetUrlSchemeHandler(new SchemeHandler(this), urlScheme: "app");

                // Legacy Developer Extras setting.
                var enableWebDevTools = ((HybridWebView.HybridWebView)(handler as HybridWebViewHandler).VirtualView).EnableWebDevTools;
                config.Preferences.SetValueForKey(NSObject.FromObject(enableWebDevTools), new NSString("developerExtrasEnabled"));
                var platformView = new MauiWKWebView(RectangleF.Empty, handler as HybridWebViewHandler, config);

                if (OperatingSystem.IsMacCatalystVersionAtLeast(major: 13, minor: 3) ||
                    OperatingSystem.IsIOSVersionAtLeast(major: 16, minor: 4)) {
                    // Enable Developer Extras for Catalyst/iOS builds for 16.4+
                    platformView.SetValueForKey(NSObject.FromObject(enableWebDevTools), new NSString("inspectable"));
                }

                return platformView;
            };
#endif

I tested and this does seem to work. I have an inline video autoplaying now.

Long Term Solution?

The simplest solution I can think of would be to just provide us with an Action<WKWebViewConfiguration> customConfiguration in iOS/MacCatalyst we can set into the HybridWebView or the Handler somehow before this function is run. It can get utilized inside that existing function like this:

        protected override WKWebView CreatePlatformView() {
                
                var config = new WKWebViewConfiguration();
                //=================================================
                customConfiguration?.Invoke(config); //modify the config file on the way through here
                //=================================================

                //then continue default programming built in
                config.UserContentController.AddScriptMessageHandler(new HybridWebView.WebViewScriptMessageHandler(MessageReceived), "webwindowinterop");
                config.SetUrlSchemeHandler(new SchemeHandler(this), urlScheme: "app");

Or otherwise I am not sure how you guys would want to fix this.

What do you think @Eilon ? Or @Redth ?

0.0.0.0 referer causing issues

I'm trying to integrate Stripe payment elements using HTML and Javascript and enable apple pay and google pay. However Stripe requires us to register domain. Can we update the referer from 0.0.0. to mycomain.com

Fix possible hang in Proxy code for Android

In #43 a new proxy feature was introduced. Some of the code does sync-over-async, and calls .Wait() to block the async call. Unfortunately on iOS/MacCat this seems to hang 100% of the time. On Android there is similar code but doesn't seem to hang (but presumably it could).

We need to investigate a pattern to avoid the calls to .Wait() by finding a proper way to do async on iOS/MacCat/Android for these calls.

Example problematic call: https://github.com/Eilon/MauiHybridWebView/pull/43/files#diff-7580ae2f86a455f984e4ba3b09acf21b36bd79c70efa55d467f2df93a6c6f136R54

Latest XCode/iOS no longer calling CreatePlatformView?

Not sure why this is happening, but it looks like the latest Xcode with iOS 17 is no longer calling: CreatePlatformView of the handler. Moving the config.UserContentController.AddScriptMessageHandler back to InitializeHybridWebView works fine.

I suspect this is a bug in MAUI and an incompatibility with iOS 17, but just curious if you are experiencing the same.

Any sample code for Web Camera Permission?

Hello Eilon! We have a new mobile project in our company to look exactly the same as the website which was already deployed. We consider to use a WebView where i come across your Project. My question is, can i ask a Camera Permission when the page just opened? Thank you

Control Throws a Null Exception When Hosted on a ContentView

I am very new to this. The requirement I have calls for the webview to be hosted in a control (contentView). The HybridView shows fine and callling JS methods etc works. However, when I close the popup window that is hosting the control there is an object reference not set to an instance of an object exception on the OnHandlerChanged --> NavigationCore.

NOTE: This app uses Mopups for pushing/popping pages.

Exception:Value cannot be null. (Parameter 'json')

It works but throw this Exception: Value cannot be null. (Parameter 'json')
my cs code:

try
{
				await hybridWebView.InvokeJsMethodAsync<int>("TestFun", 123, 456);
}
catch (Exception ex)
{
				Debug.WriteLine(ex.Message);
}

my xaml code:

<ContentPage
    x:Class="ActionTracer.MainPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:ejl="clr-namespace:HybridWebView;assembly=HybridWebView">
    <ejl:HybridWebView
        x:Name="hybridWebView"
        HybridAssetRoot="hybrid"
        MainFile="index.html"
        MaximumWidthRequest="600"
        MinimumHeightRequest="350"
        RawMessageReceived="hybridWebView_RawMessageReceived" />
</ContentPage>

my html code:

<!DOCTYPE html>
<html lang="">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, 
minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover, user-scalable=no">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>
        <%= htmlWebpackPlugin.options.title %>
    </title>
    <script src="_hwv/HybridWebView.js"></script>
</head>
<script>
    function TestFun(value1, value2) {
        alert(value1 + " " + value2);
    }
</script>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.