1

I spent two days reading/trying every possible solution I can find/think of, but none of them worked, I am totally stuck. Any direction would be highly appreciated!

I am trying to read the Chrome browsing history via the Chrome extension I am developing. Since this history file(a sqlite3 database) is located locally, I need to set up the native messaging for my extension to read it. I am using Chrome 84, Ubuntu. I followed this example from Chrome developer site, but it always Failed to connect: Native host has exited. I launch this extension at chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/main.html.

  1. My native messaging host(native-messaging-example-host file, below at the bottom of this post) is written in python 2, but how does chrome know to run it as python 2?

  2. How does native messaging host send message to my exposed page? I read its code, but I am not familiar with all the modules, threads it uses.

  3. Chrome site mentioned that Native host has exited is likely due to "The pipe to the native messaging host was broken before the message was read by Chrome. This is most likely initiated from your native messaging host.", but it did not mention how to fix it. I tried (1) changing its permission from -rwxr--r-- to-rwxrwxrwx, (2) moving the JSON file to an easier access folder and change the path value accordingly. My question is, what are the possible reasons to cause "the pipe to the native messaging host was broken before the message was read by Chrome"?

My /home/username/.config/google-chrome/NativeMessagingHosts/com.google.chrome.example.echo.json:

{
  "name": "com.google.chrome.example.echo",
  "description": "Chrome Native Messaging API Example Host",
  "path": "/home/moon/BrowserExtensions/nativeMessage/host/native-messaging-example-host",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"
  ]
}

My app folder is exact same as the example mentioned above, it has 4 files: icon-128.png main.html main.js manifest.json. The manifest.json:

{
  "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcBHwzDvyBQ6bDppkIs9MP4ksKqCMyXQ/A52JivHZKh4YO/9vJsT3oaYhSpDCE9RPocOEQvwsHsFReW2nUEc6OLLyoCFFxIb7KkLGsmfakkut/fFdNJYh0xOTbSN8YvLWcqph09XAY2Y/f0AL7vfO1cuCqtkMt8hFrBGWxDdf9CQIDAQAB",
  "name": "Native Messaging Example",
  "version": "1.0",
  "manifest_version": 2,
  "description": "Send a message to a native application.",
  "app": {
    "launch": {
      "local_path": "main.html"
    }
  },
  "icons": {
    "128": "icon-128.png"
  },
  "permissions": [
    "nativeMessaging"
  ]
}

The main.js file:

var port = null;

var getKeys = function(obj){
   var keys = [];
   for(var key in obj){
      keys.push(key);
   }
   return keys;
}


function appendMessage(text) {
  document.getElementById('response').innerHTML += "<p>" + text + "</p>";
}

function updateUiState() {
  if (port) {
    document.getElementById('connect-button').style.display = 'none';
    document.getElementById('input-text').style.display = 'block';
    document.getElementById('send-message-button').style.display = 'block';
  } else {
    document.getElementById('connect-button').style.display = 'block';
    document.getElementById('input-text').style.display = 'none';
    document.getElementById('send-message-button').style.display = 'none';
  }
}

function sendNativeMessage() {
  message = {"text": document.getElementById('input-text').value};
  port.postMessage(message);
  appendMessage("Sent message: <b>" + JSON.stringify(message) + "</b>");
}

function onNativeMessage(message) {
  appendMessage("Received message: <b>" + JSON.stringify(message) + "</b>");
}

function onDisconnected() {
  appendMessage("Failed to connect: " + chrome.runtime.lastError.message);
  port = null;
  updateUiState();
}

function connect() {
  var hostName = "com.google.chrome.example.echo";
  appendMessage("Connecting to native messaging host <b>" + hostName + "</b>")
  port = chrome.runtime.connectNative(hostName);
  port.onMessage.addListener(onNativeMessage);
  port.onDisconnect.addListener(onDisconnected);
  updateUiState();
}

document.addEventListener('DOMContentLoaded', function () {
  document.getElementById('connect-button').addEventListener(
      'click', connect);
  document.getElementById('send-message-button').addEventListener(
      'click', sendNativeMessage);
  updateUiState();
});

The native-messaging-example-host file:


import struct
import sys
import threading
import Queue

try:
  import Tkinter
  import tkMessageBox
except ImportError:
  Tkinter = None

# On Windows, the default I/O mode is O_TEXT. Set this to O_BINARY
# to avoid unwanted modifications of the input/output streams.
if sys.platform == "win32":
  import os, msvcrt
  msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
  msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)

# Helper function that sends a message to the webapp.
def send_message(message):
   # Write message size.
  sys.stdout.write(struct.pack('I', len(message)))
  # Write the message itself.
  sys.stdout.write(message)
  sys.stdout.flush()

# Thread that reads messages from the webapp.
def read_thread_func(queue):
  message_number = 0
  while 1:
    # Read the message length (first 4 bytes).
    text_length_bytes = sys.stdin.read(4)

    if len(text_length_bytes) == 0:
      if queue:
        queue.put(None)
      sys.exit(0)

    # Unpack message length as 4 byte integer.
    text_length = struct.unpack('i', text_length_bytes)[0]

    # Read the text (JSON object) of the message.
    text = sys.stdin.read(text_length).decode('utf-8')

    if queue:
      queue.put(text)
    else:
      # In headless mode just send an echo message back.
      send_message('{"echo": %s}' % text)

if Tkinter:
  class NativeMessagingWindow(Tkinter.Frame):
    def __init__(self, queue):
      self.queue = queue

      Tkinter.Frame.__init__(self)
      self.pack()

      self.text = Tkinter.Text(self)
      self.text.grid(row=0, column=0, padx=10, pady=10, columnspan=2)
      self.text.config(state=Tkinter.DISABLED, height=10, width=40)

      self.messageContent = Tkinter.StringVar()
      self.sendEntry = Tkinter.Entry(self, textvariable=self.messageContent)
      self.sendEntry.grid(row=1, column=0, padx=10, pady=10)

      self.sendButton = Tkinter.Button(self, text="Send", command=self.onSend)
      self.sendButton.grid(row=1, column=1, padx=10, pady=10)

      self.after(100, self.processMessages)

    def processMessages(self):
      while not self.queue.empty():
        message = self.queue.get_nowait()
        if message == None:
          self.quit()
          return
        self.log("Received %s" % message)

      self.after(100, self.processMessages)

    def onSend(self):
      text = '{"text": "' + self.messageContent.get() + '"}'
      self.log('Sending %s' % text)
      try:
        send_message(text)
      except IOError:
        tkMessageBox.showinfo('Native Messaging Example',
                              'Failed to send message.')
        sys.exit(1)

    def log(self, message):
      self.text.config(state=Tkinter.NORMAL)
      self.text.insert(Tkinter.END, message + "\n")
      self.text.config(state=Tkinter.DISABLED)


def Main():
  if not Tkinter:
    send_message('"Tkinter python module wasn\'t found. Running in headless ' +
                 'mode. Please consider installing Tkinter."')
    read_thread_func(None)
    sys.exit(0)

  queue = Queue.Queue()

  main_window = NativeMessagingWindow(queue)
  main_window.master.title('Native Messaging Example')

  thread = threading.Thread(target=read_thread_func, args=(queue,))
  thread.daemon = True
  thread.start()

  main_window.mainloop()

  sys.exit(0)


if __name__ == '__main__':
  Main()

Thank you!!!

moon
  • 531
  • 1
  • 5
  • 24
  • 1) JSON is just an info file needed to locate the host app. 2) Try checking chrome_debug.log per [this article](https://www.chromium.org/for-testers/enable-logging). Don't forget to completely exit all Chrome processes e.g. by using the Exit command in the browser menu. – wOxxOm Sep 09 '20 at 14:05
  • Thanks! @wOxxOm, I'm not sure if this is normal behavior: when I open `google-chrome` in terminal, the browser does open but `google-chrome` stops right after saying `NaCl helper process running without a sandbox! Most likely you need to configure your SUID sandbox correctly`. I read the `log.txt` from `--enable-logging=stderr --v=1 > log.txt 2>&1`, it seems not affected by my action of interacting with the extension. Do you think I need to fix that sandbox issue? or there is something else I can look into? – moon Sep 09 '20 at 15:40
  • NaCl is another kind of plugin system that you don't use so it doesn't affect you. Inspect chrome_debug.log for clues. – wOxxOm Sep 09 '20 at 15:49
  • Thanks! @wOxxOm. I closed all Chrome windows using `Exit` in the browswer, then run `google-chrome --enable-logging=stderr --v=1 > log.txt 2>&1`, but nothing in the `chrome_debug.log`, it seems not being updated at all, its latest change happens 2 hours ago. I noticed there is another file called `chrome_shutdown_ms.txt`, it gets updated every time I run chrome but again it's an empty file. This is probably too much to get help in the comment here, I will keep reading that article. Thank you very much! – moon Sep 09 '20 at 16:11
  • Hi@wOxxOm, one quick question, do you think there is something wrong with my native messaging host? it's written in python 2, I just added its script to the bottom of my question. My interaction with the tkinter window runs smoothly, no error info. I tried commenting out the `sys.exit(0)`, but no luck. – moon Sep 09 '20 at 16:33
  • The host looks like it's an exact copy of the one in chromium source so it should be okay. Can't help more, but just in case see [Shebang line for Python 2.7](https://stackoverflow.com/q/21189346) – wOxxOm Sep 09 '20 at 18:31

0 Answers0