Native-messaging API--catching a connection request failure versus a genuine disconnection

Is it possible to catch a connection error when attempting to connect to a native application?

nativeApp.port = browser.runtime.connectNative( "native_app_name" );

The above doesn’t return a promise but a port. I tried testing the port.error property for null, which it is null if a port is returned; but it is also null immediately after if a bad app name is provided. The error doesn’t appear to be generated until sometime later and the onDisconnect event is fired.

If write the port object to the console right after the connection request, in collapsed form, the error property displays null; but, when expanded, it displays an error object.

If write the value of port.error to the console, it displays null.

Am I doing something wrong? Why does onDisconnect fire when it could not connect, since the app name provided was intentionally wrong and doesn’t exist?

Is there a way to distinguish between a failure to connect and a dropped connection?

Thank you.

I think the port.onDisconnect.addListener could be used to detect connection issues - the even handler will contain information about error, see the docs:
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/Port#Type

port.onDisconnect.addListener((p) => {
  if (p.error) {
    console.log(`Disconnected due to an error: ${p.error.message}`);
  }
});

Thank you. I’ve been using the onDisconnect listener and it does provide an error message that appears to be the error property of the port object. I was asking about the timing between the connect request and the onDisconnect event. The connect request doesn’t appear to be synchronous but doesn’t return a promise either; so, you wait for a disconnect error, even though there was never a connection from which to disconnect, and check the text string for the error.

Perhaps a dropped connection can be simulated by ending the native app process in task manager to see what the error message might be in that case.

Somewhere along the line an error will be caught that the extension can’t communicate with the native application. I just wanted to know if there was something more specific than a text string, such as a code or returning NULL rather than a port object when a connection could not be established. But the onDisconnect event will suffice; it’ll have to.

Well, maybe someone else can respond here because this is a bit outside my expertise.
But if look at the runtime.connect” docs, it says for return value:

runtime.Port . Port through which messages can be sent and received. The port’s onDisconnect event is fired if the extension does not exist.

Anyway, the return value for both connect and connectNative is a Port object (not a promise) so because of the “single-thread” nature of JavaScript, nothing else can run in the meantime so if you register onDisconnect right away on the Port object, it must fire if the connection fails - because the connection doesn’t exists yet - at least I can’t imagine it could without a Promise involved - all IO operations are always non-blocking to prevent freezing.

Thanks. I’m not trying to complain about it, if it comes across like that.

Regardless of whether or not the request for a port was successful, nativeApp.port.error is null immediately after the request. The onDisconnect event does fire and at that stage the value is No such native application NatAppName.

nativeApp.port = browser.runtime.connectNative( "NatAppName" );
console.log( nativeApp.port );
// This won't work.
if ( nativeApp.port.error )
    {
      console.log( "Error connecting to nativeApp." );
      return;
    }
else
    console.log( "Communication port established." );

nativeApp.port.onDisconnect.addListener( ( p ) =>
   {
     console.log( p );
      if ( p.error )
         console.log( `Disconnected due to an error: ${ p.error.message }` );
   } );

It’s like browser.runtime.connectNative() is an asynchronous request that only tells you when it fails and not when it succeeded?

I don’t know how else it could be done, except perhaps like an indexedDB request has the result property in the successful request object, such that the port could be assigned after the connection is known to be successful.

It works as it is; I just wanted to make sure I wasn’t missing something. Thanks again.

1 Like

You are right:
runtime.Port objects do not have an “onConnectionSuccessful” event.

There are only two events you can listen for:

  • onDisconnect
  • onMessage

So you have to make do with them.

  1. Connect to the native app
  2. Send a test message to the native app
  3. Listen for both events

If the connection attempt was successful, the onMessage handler is called.
If not, the onDisconnect handler is called.

I’ve attached a test extension and native app.
The python file is zipped because this forum only lets you attach:
jpg, jpeg, png, gif, pdf, ics, zip

testconnectionbasedmessaging.zip (4.5 KB)
testconnectionbasedmessaging.py.zip (790 Bytes)

1 Like

Thank you for taking the time to put this together.

I believe I understand what your stating here and have had a similar set up.

The onDisconnect event will fire if the connection attempt fails regardless of whether an attempt to post a message to the native application is made. If that was ignored, the attempt to post a message to the native app would fail also, but it returns nothing but undefined whether successful or not.

I wanted to distinguish between a failed attempt to connect and a dropped connection; so, the text message of the error object appears to be the only way to do that, apart from maybe the columnNumber, fileName, lineNumber properties. If the process conhost.exe is ended via the task manager, assuming that closes the native app, the onDisconnect event doesn’t fire. I need to test what takes place when the native application closes due to programmatic exit or error, and when the extension closes the connection because it received an invalid JSON string. Perhaps, one of the those three properties makes a distinction. I’m pretty sure an invalid string fires the onDisconnect event.

I was considering providing a message that states, failed to connect, native app error/exited, extension closed the app–please click to try to reconnect. But that distinction is not necessary.

If the onDisconnect doesn’t fire for all of these cases, then subsequent attempts to communicate with the native application will fail silently and the extension will appear unresponsive. So, it’s a bit more important than just wanting a descriptive error message. But, as I wrote, I’ve a lot more to test and I need more on the native app side to do so properly.

I wondered about when a ping to the native app should be made to confirm that a connection has been established, since it appeared to be asynchronous in regards to being able to test port.error immediately after making the request to open a port. But you can post a message on the port immediately after making the request and it works.

nativeApp.port = browser.runtime.connectNative( "AppName" );
console.log( nativeApp.port.postMessage( "hello" ) ); 

It returns undefined but it works. Why does it work but the error message is delayed? Perhaps it is queued like writing the indexedDB event handlers after making the database request. I don’t know.

Thanks again.

I want to do something that is either very simple, or impossible.

I wrote a translation program in Python. It reads text or HTML from stdin until EOF, processes it for a couple of seconds, and writes the translated version to stdout. It works well.

I want to write a Firefox extension that passes every webpage to this program, in one piece so as not to break sentences, and displays the translated version.

I successfully installed emoji-substitution and native-messaging, but I need a raw HTML file, not a parsed DOM tree. Once I get it, I need to pass it to the native app, wait for the translation, and display it.

Are you asking how to get the raw HTML file? Perhaps something like this to convert the HTML DOM tree to a string for your translator to modify and then pass back to the DOM?

https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer/serializeToString

And these methods to add it back.

https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer#Inserting_nodes_into_a_DOM_based_on_XML

Hi Gary,

 Your reply was most helpful. In the content script I did:

var s = new XMLSerializer();
var str = s.serializeToString(document);
browser.runtime.sendMessage(str);

 This converts the displayed document to HTML and passes it

to the background script, which passes it to a Bash script, which
passes it to a Python program, which performs the translation.
The new HTML file then travels back the way it came, ending up
as a string variable in the content script.

 One last, really stupid question: How do I display it?

 It must replace the contents of the current tab, without

triggering the content script to run again.

				Dave Coffin  1/24/2021

The second link in the previous message shows some methods of getting the translated HTML string back into the document. As long as you don’t reload the page, I don’t see why the content script would run again.

I suppose, to test your code, you could use insertAdjacentHTML() to start.

I think it causes problems if you try to replace an entire document because you will likely lose your scripts, at least your event listeners. This SO question explains why.

I tried to replace an entire HTML file in place without reload and lost all the event listeners:

fetch('file.html')
.then( response => response.text() )
.then( result => {
        let parser = new DOMParser(),
            doc = parser.parseFromString( result, 'text/html' );
        document.replaceChild( doc.documentElement, document.documentElement );
    } );

There are several options that I can think of and probably better ones that I don’t know about. One is you don’t have to serialize the entire document, but could try only the body element. Or, you could still serialize the entire document, then use DOMParser to convert it, and extract the nodes you need from it and replace them in the document, using document.importNode() and node.replaceChild() or childNode.replaceWith() and forgot replaceChildren() for convenience.

Some MDN links on DOMParser: 1, 2, 3.

I don’t know what can or cannot be approved in an extension concerning adding content by HTML string. The documents read that one cannot use innerHTML and I’m not sure about insertAdjacentHTML. I think it is because scripts can be injected that way. If a DOMParser is used, as described in this Mozilla Discourse question at response 15, the scripts can be removed.

If the extension wouldn’t be approved using insertAdjacentHTML, depending upon what you’re translating, perhaps you could traverse the document for all nodes that have textContent, keep the node references in a JS array and pass an array of the text in the same order. Translate it and pass it back to the JS, and replace thetextContent of all the nodes using the stored references.

There are many methods for traversing and manipulating the DOM such that it would appear that you could accomplish what you want using some set of methods. I’m far from an expert, but you’d think that if you serialized the entire document to string and used a DOMParser to convert your translated string into a new HTML document, excluding script elements, there’d be a one-to-one correspondence between the DOM trees of the translated and untranslated documents, such that you could walk through the translated document and update node-by-node if necessary. That is, if you couldn’t replace the whole DOM tree at once.

This may just be more confusing than helpful but I hope you can find something useful in it.