Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

External keyword in JS for iOS and maccatyalist is deprecated. #2775

Open
SoumyadipYT-OSS opened this issue Feb 12, 2025 · 8 comments
Open

Comments

@SoumyadipYT-OSS
Copy link

SoumyadipYT-OSS commented Feb 12, 2025

Type of issue

Outdated article

Description

Please update the code of HybridWebView.js part:
because external keyword is deprecated: I have updated the code block please add into it.

window.HybridWebView = {
    "Init": function Init() {
        function DispatchHybridWebViewMessage(message) {
            const event = new CustomEvent("HybridWebViewMessageReceived", { detail: { message: message } });
            window.dispatchEvent(event);
        }

        if (window.chrome && window.chrome.webview) {
            // Windows WebView2
            window.chrome.webview.addEventListener('message', arg => {
                DispatchHybridWebViewMessage(arg.data);
            });
        }
        else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
            window.webkit.messageHandlers.webwindowinterop.postMessage = message => {
                DispatchHybridWebViewMessage(message);
            };
        }
        else {
            // Android WebView
            window.addEventListener('message', arg => {
                DispatchHybridWebViewMessage(arg.data);
            });
        }
    },

    "SendRawMessage": function SendRawMessage(message) {
        window.HybridWebView.__SendMessageInternal('__RawMessage', message);
    },

    "InvokeDotNet": async function InvokeDotNetAsync(methodName, paramValues) {
        const body = {
            MethodName: methodName
        };

        if (typeof paramValues !== 'undefined') {
            if (!Array.isArray(paramValues)) {
                paramValues = [paramValues];
            }

            for (var i = 0; i < paramValues.length; i++) {
                paramValues[i] = JSON.stringify(paramValues[i]);
            }

            if (paramValues.length > 0) {
                body.ParamValues = paramValues;
            }
        }

        const message = JSON.stringify(body);

        var requestUrl = `${window.location.origin}/__hwvInvokeDotNet?data=${encodeURIComponent(message)}`;

        const rawResponse = await fetch(requestUrl, {
            method: 'GET',
            headers: {
                'Accept': 'application/json'
            }
        });
        const response = await rawResponse.json();

        if (response) {
            if (response.IsJson) {
                return JSON.parse(response.Result);
            }

            return response.Result;
        }

        return null;
    },

    "__SendMessageInternal": function __SendMessageInternal(type, message) {

        const messageToSend = type + '|' + message;

        if (window.chrome && window.chrome.webview) {
            // Windows WebView2
            window.chrome.webview.postMessage(messageToSend);
        }
        else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
            // iOS and MacCatalyst WKWebView
            window.webkit.messageHandlers.webwindowinterop.postMessage(messageToSend);
        }
        else {
            // Android WebView
            hybridWebViewHost.sendMessage(messageToSend);
        }
    },

    "__InvokeJavaScript": function __InvokeJavaScript(taskId, methodName, args) {
        if (methodName[Symbol.toStringTag] === 'AsyncFunction') {
            // For async methods, we need to call the method and then trigger the callback when it's done
            const asyncPromise = methodName(...args);
            asyncPromise
                .then(asyncResult => {
                    window.HybridWebView.__TriggerAsyncCallback(taskId, asyncResult);
                })
                .catch(error => console.error(error));
        } else {
            // For sync methods, we can call the method and trigger the callback immediately
            const syncResult = methodName(...args);
            window.HybridWebView.__TriggerAsyncCallback(taskId, syncResult);
        }
    },

    "__TriggerAsyncCallback": function __TriggerAsyncCallback(taskId, result) {
        // Make sure the result is a string
        if (result && typeof (result) !== 'string') {
            result = JSON.stringify(result);
        }

        window.HybridWebView.__SendMessageInternal('__InvokeJavaScriptCompleted', taskId + '|' + result);
    }
}

window.HybridWebView.Init();

Page URL

https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/hybridwebview?view=net-maui-9.0

Content source URL

https://github.com/dotnet/docs-maui/blob/main/docs/user-interface/controls/hybridwebview.md

Document Version Independent Id

2a6b2f14-d1b6-4833-6193-1d3ddb123ca0

Article author

@davidbritch

Metadata

  • ID: 2a6b2f14-d1b6-4833-6193-1d3ddb123ca0
  • PlatformId: 2316decc-0e74-6afe-7d91-e16c8177e14a
  • Service: dotnet-mobile
  • Sub-service: dotnet-maui

Related Issues

@Eilon
Copy link
Member

Eilon commented Feb 12, 2025

Hi @SoumyadipYT-OSS , it looks like the HybidWebView code was only using window.external on iOS/MacCatalyst, where there was apparently never support for it in the browser anyway. The code for HybidWebView was entirely overwriting window.external with its own functions for C#/JS interop.

Are you seeing any problems or issues with this usage in HybridWebView?

@SoumyadipYT-OSS
Copy link
Author

@Eilon Thank you for the previous reply, I am facing an issue to build HybridWebView handlers in .NET MAUI, I tried to configure but the index.html file is not rendering in the application. Can you help me to guide me on this topic?

@SoumyadipYT-OSS
Copy link
Author

@Eilon can you guide for HybridWebView implementation for a existing normal .NET MAUI App? Because it is not working properly in my application.
I am using .NET 9 sdk: 9.0.200 version.

@Bricobit
Copy link

I've had a hard time creating a bidirectional communication between Android/Windows and the JavaScript of the index with window.external. When I finally got everything working, I learned that window.external is deprecated. Now I've started from scratch, and there's no way for the native app to receive messages from the JavaScript of the index.

I created this class:

`namespace ProjectName {
public class HybridWebView : WebView {
public Action? MessageReceived { get; set; }

    public HybridWebView() {
        Navigated += OnNavigated;
    }

    private void OnNavigated(object? sender, WebNavigatedEventArgs e) {
        this.Eval(@"window.HybridWebView = {
                        invokeCSharpAction: function(message) {
                            window.dispatchEvent(new CustomEvent('invokeCSharpAction', { detail: message }));
                        }
                    };");

        // Listen to the event from JavaScript
        this.Eval(@"window.addEventListener('invokeCSharpAction', function(event) {
                            invokeCSharpAction(event.detail);
                        });");
    }

    public async Task SendMessageToJsAsync(string message) {
        await EvaluateJavaScriptAsync($"window.receiveMessage('{message}')");
    }

    public void ReceiveMessageFromJs(string message) {
        MessageReceived?.Invoke(message);
    }
}

}
`

And from JavaScript, I'm doing this:

`document.addEventListener("DOMContentLoaded", function() {
// Define HybridWebView and the method invokeCSharpAction
window.HybridWebView = {
invokeCSharpAction: function(message) {
window.dispatchEvent(new CustomEvent("invokeCSharpAction", { detail: message }));
}
};
});

function SendDataToNative(command, params = null) {
const data = params == null ? { command: command } : { command: command, params: params };
window.HybridWebView.invokeCSharpAction(JSON.stringify(data));
}

window.receiveMessage = function(message) {
const data = JSON.parse(message);
const command = data.command;
const params = data.data;

if (command === 'location') {
    G.AppStart.webview(params.lat, params.lon);
} else if (command === 'etc') {
    // Other command handling
}

};

// Example usage
SendDataToNative('getLocation', null);
SendDataToNative('allData', { work: { dtm: '2023-09-25' } });
`
And in MAUI .NET 9, I have this:

`// Somewhere in the class
browser.MessageReceived = HandleMessageFromJs;

private void HandleMessageFromJs(string message) {
DisplayAlert("Received:", message, "OK");
var data = JObject.Parse(message);
OnDataFromJs(data);
}
`
And there's no way for the native app to receive any messages.

@SoumyadipYT-OSS
Copy link
Author

@Bricobit Due to this deprecated issue, the log is not showing the message properly and so the output is not rendering on the UI part. But if you check in the backend, try to write Console.WriteLine(); If answer shows in the output, then your backend is working fine!

@Bricobit
Copy link

In the end, in order to avoid using windows.external, which is deprecated, I have tried to navigate directly with window.location.href and for the moment everything seems to be working fine and I have not had to change anything I had done. It seems that it has some limitations when passing some complex json objects through the URL parameters, but for the moment it works for me.

<!DOCTYPE html><html><head>
	<meta charset = "utf-8">
	<link href    = "data:," rel = "icon"/>
	<script src   = "app/classes/start/AutoImport.js" charset="utf-8"></script>
	<script type  = "text/javascript">
	/*
	In order not to dirty the dom with many variables we create a single global object with 
	properties that will contain values or objects that will be accessible to the entire program.
	Additionally, to avoid losing control, we sealed the class so that properties cannot be 
	added on the fly from another part of the application.
	*/
	/*global var*/ const G /*:Object*/ = new ProxySeal(new Index(<?php echo getUrlParams(); ?>,'1.2.2'));
	 
	// Function to send data to the native application, whether Android, IOS/MAC or Windows
	/*global public*/ function SendDataToNative(command /*:String*/,params/*:Object*/=null) {
		window.location.href = 'app://message?' + encodeURIComponent(JSON.stringify(params == null ? { command: command } : { command: command,params:params }));
	}

	// Function to receive data from the native application, whether Android, IOS/MAC or Windows
	/*global public*/ function OnDataFromNative(command /*:String*/,params/*:Object*/=null)/*:void*/{
			   if (command == 'location') {G.AppStart.webview(params.lat, params.lon);
		} else if (command == 'etc') {// Other command handling
		} else if (command == 'etc') {// Other command handling
		} else if (command == 'etc') {// Other command handling
		} else if (command == 'etc') {// Other command handling
		}
	}

</script></head></html>

@SoumyadipYT-OSS
Copy link
Author

@Bricobit Check my code that I have solved the code, check on top on this issue, the whole code you can use the code adjust as your project. Thank you! If you want to discuss on this topic, issue is still open.
Happy Coding!

@Bricobit
Copy link

Hello, thanks, that was the first thing I tried. I put your window.HybridWebView = {...} object inside my index.php, but for some reason, I couldn't retrieve the messages sent from index.php to MAUI. It's possible that my limited experience in C# is causing me not to implement it correctly, or I might be missing a practical working example.

In the end, I opted for a hybrid solution, because there can be character issues when sending JSON objects through URLs, and I don't want to send Base64 strings, since URLs also have character limits, and I don't want to worry about that, although it's unlikely I'll need to send that much information.

The method I've decided to use is to first store the JSON object or information in localStorage and then send a message/command to MAUI via window.location.href with a URL like ?command=loadFromLocalStorage&key=keyToLoad, to indicate/notify it to load the information from localStorage for the given key when it receives that command. So far, this is working well for me.

To communicate from MAUI to JS, I do it directly by evaluating a function, and for reverse communications where I need to send complex data, I use localStorage as an intermediary, as I have explained.

I will keep an eye on the community in case someone shows a basic example of how to implement hybridwebview and see how something like a "Hello world" is sent and received correctly and then I may change the system, greetings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants