macosxamarin.formschrome-native-messaging

Cross-Browser, Cross-Platform native messaging using Xamarin Mac, C#


I tried to recreate the example to connect to my Xamarin Mac application using a chrome extension I will use as backbone for all other browsers, but it didn't work with the following error:

Error when communicating with the native messaging host.

The documentation seems straightforward on paper, but isn't that simple in the real life.

Here are my Read and listen functions (great examples there,https://github.com/anewtonlevey/NativeMessaging):

    public void Listen()
    {

        JObject data;
        while ((data = Read()) != null)
        {
            ProcessReceivedMessage(data);
        }

    }




    private JObject Read()
    {
        try
        {
            Stream stdin = Console.OpenStandardInput();


            byte[] lengthBytes = new byte[4];
            stdin.Read(lengthBytes, 0, 4);


            var buffer = new char[BitConverter.ToInt32(lengthBytes, 0)];

            using (var reader = new StreamReader(stdin)) while (reader.Peek() >= 0) reader.Read(buffer, 0, buffer.Length);
            return JsonConvert.DeserializeObject<JObject>(new string(buffer));


        }
        catch (Exception e)
        {
            Console.WriteLine("Read error: {0}", e);
            return null;
        }
    }

From my main function , I call them using the following:

        host = new ExtendedHost();


        host.Listen();

To note that I am using native messaging from Xamarin forms for a macOS application, as from my point of view, it might be the endianness, as the following documentation suggests:

Error when communicating with the native messaging host:

https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host-protocol

Another window of my C# app seems to want to open when I am sending the message from the web extension, meaning that the error seems to occur at the reading step.

Help much appreciated.

Thanks.

Edit:

I suggested endianness as the documentation suggest that the response should be correct as well or would render the whole process corrupted, as suggested in the native messaging protocol doc, but it is not the issue for sure as I should still be able to parse the data sent:

https://developer.chrome.com/apps/nativeMessaging#native-messaging-host-protocol

I saw that the response has something to do with it, and that the function used to send message(as it was suggested that error in response could cause an overall error) would cause the issue, or that Console.StandardInput() doesn't work as suggested, but I don't think so as well.

My thought is that trying to attach to the running app caused the issue as I used ps aux | grep app_name, and put the path within the host file.

Here is the mention in the protocol:

Native messaging protocol Chrome starts each native messaging host in a separate process and communicates with it using standard input (stdin) and standard output (stdout).

I also saw this answer from Xan in another post that Native messaging can't attach to running processes and Chrome should launch the process:

Talk to MacOSX app from Chrome Extension

Not to forget that I am using the native messaging API with Xamarin macOS in Debug mode, and that my app is a macOS app.

More informations are welcome on that subject, such as:

Of course, if this is what you think is causing the issue.

Thanks in advance!

EDIT:

When sending output to stderr, I got the following error:

Read error: System.NotSupportedException: Stream does not support reading at System.IO.FileStream.Read (System.Byte[] array, System.Int32 offset, System.Int32 count) [0x0002e] in /Library/Frameworks/Xamarin.Mac.framework/Versions/Current/src/Xamarin.Mac/mcs/class/corlib/System.IO/FileStream.cs:493

Could it be that Console.OpenStandardInput doesn't work cross platform? And if so, what could be another method to parse the standard input, as I saw that pipes could work, but I am not sure if they can deployed in a cross-platform fashion.

I didn't got any error or output when reading, only the above error at starting of the app. For me, OpenStandardInput seems to be the culprit.

Any thoughts appreciated! Thanks


Solution

  • Basically, the first 32 bits sent from the extension corresponds to the message length, in native order.

    Since the first 32 bits are of type int, and I tried to parse JSON objects, Error when communicating with the native messaging host is raised. The documentation mentions that the issue resides within a broken Native Messaging protocol.

    The worst part is that I saw it on the Python example, but thought that this was implemented by the programmer who did it.

    I stumbled upon this thread on Stack Overflow which helped confirm, (Chrome App fails to communicate with native host on windows).

    To debug Chrome in a much more verbose fashion, launch Chrome with --enable-logging=stderr --v=1 in the command line, and all stderr will be outputted there.

    The documentation is mentioning it, but the examples found here and there using C# don't include them.

    If I have time, I will confirm that this was the issue by editing this answer, and provide code for getting this to work on Visual Studio for macOS.

    Edit

    I managed to have this to work a few days ago. I modified the code so that it works without the while loop, as the StreamReader reads stdin until the end, thus not requesting a while loop (this caused my previous code to hang). The SendMessage method also differs from other seen in other examples. Fewer lines, and works just fine.

    My working example:

    public void Listen()
    {
    
    
        JObject data;
    
        //while ((data = Read()) != null)
        //{
        //    ProcessReceivedMessage(data);
        //}
    
        if((data = Read()) != null)
        {
            string message = ProcessReceivedMessage(data);
            SendMessage(message);
        }
    }
    
    
        private JObject Read()
        {
    
            Stream stdin = Console.OpenStandardInput();
    
            byte[] lengthBytes = new byte[4];
            stdin.Read(lengthBytes, 0, lengthBytes.Length);
    
            char[] buffer = new char[BitConverter.ToInt32(lengthBytes, 0)];
    
            var reader = new StreamReader(stdin);
            //int messageLength =
            reader.Read(buffer, 0, buffer.Length);
    
            return JsonConvert.DeserializeObject<JObject>(new string(buffer));
        }
    
    
    
    
    
        public void SendMessage(JToken data)
        {
            var bytes = Encoding.UTF8.GetBytes(data.ToString(Formatting.None));
            var lengthBytes = BitConverter.GetBytes(bytes.Length);
            var stdout = Console.OpenStandardOutput();
    
            stdout.Write(lengthBytes, 0, 4);
            stdout.Write(bytes, 0, bytes.Length);
            stdout.Flush();
        }