4

I am trying to implement a chrome extension using runtime.connectNative and postMessage. I am following the chrome documentation, downloaded the native messaging example which I'm trying to run without any changes, while the code for the native host application can be found here.

However, I'm getting the error: Uncaught TypeError: Cannot read property 'connectNative' of undefined.

The error is being triggered from the javascript extension file, in this line:
port = chrome.runtime.connectNative(hostName);

while the extension is being loaded from the manifest like so:

"app": {
   "launch": {
      "local_path": "main.html"
   }
}

Any ideas how to solve the problem please?

Chrome version 34, tested on windows 7, 8.1

Community
  • 1
  • 1
user2707674
  • 333
  • 1
  • 6
  • 14
  • How are you launching this app? Did you load it as an unpacked extension? – rsanchez Apr 30 '14 at 12:05
  • yes I loaded whole folder as an unpacked extension, and I can see it is enabled in the list of extensions. Besides, I get any alerts triggered before the connectNative call, but not after it – user2707674 Apr 30 '14 at 12:45
  • 1
    But are you launching the app from `chrome://apps/`? I just tested it and didn't get that error. – rsanchez Apr 30 '14 at 12:51
  • 1
    just wasted a few hours of my life trying to fix a non existent bug :( I was trying to load the html file directly.. will now test using a background or event page.. thx a lot :) – user2707674 Apr 30 '14 at 13:10
  • 1
    FWIW I found it helpful to create an options page in my extension during development. When you open it (from the Extensions manager) it gives you a stable UI running inside your extension's sandbox. – Spike0xff Apr 30 '14 at 14:58
  • 1
    Seems to be answered here: http://stackoverflow.com/questions/24726026/chrome-runtime-connectnative-generates-uncaught-typeerror-undefined-is-not-a-fu – bmm6o Aug 05 '14 at 20:43

2 Answers2

8

The immediate problem is that you are not running the sample code correctly. The larger problem is that Google has not provided comprehensive documentation on how to use this sample code.

The Native Messaging example you referenced only links to the sample code for the Chrome extension. After searching around I was able to find related sample code for the native messaging host application. To get the sample code for both the Chrome extension and native messaging host application together you'll want to download nativeMessaging.zip. In that zip file you'll also find some brief instructions on how to install the native messaging host on Windows, Linux and Mac OS X. I'll tell you right now that the instructions are incomplete as they do not tell you how to install the Chrome extension. Additionally the scripts for installing and uninstalling the native messaging host do not work as-is on OS X. See below for my installation instructions and corrected scripts.

How to install the sample extension and native host application

  1. Download and unzip the nativeMessaging.zip file.
  2. Install the Chrome extension
    1. In Chrome enter chrome://extensions/ in the address bar
    2. Click the “Load unpacked extension...” button
    3. Navigate to the unzipped nativeMessaging directory and select the app directory for import
  3. Install the native messaging host application
    1. For OS X and Linux you’ll need to add execute permission to some of the files. Run the command: chmod a+rx nativeMessaging/host/install_host.sh nativeMessaging/host/native-messaging-example-host nativeMessaging/host/uninstall_host.sh
    2. For OS X you’ll need to fix some bugs in nativeMessaging/host/install_host.sh and nativeMessaging/host/uninstall_host.sh. See below for the corrected scripts.
    3. For OS X, Linux and Windows follow the instructions in nativeMessaging/README.txt
  4. Run the Chrome extension
    1. In Chrome enter chrome://apps/ in the address bar
    2. Click on the Native Messaging Example app icon
    3. After the app loads you should see a single button named “Connect.” Click that button and you should see the native messaging host application launch automatically.

Corrected nativeMessaging/host/install_host.sh

#!/bin/sh
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

set -e

DIR="$( cd "$( dirname "$0" )" && pwd )"
if [ $(uname -s) == 'Darwin' ]; then
  if [ "$(whoami)" == "root" ]; then
    TARGET_DIR="/Library/Google/Chrome/NativeMessagingHosts"
  else
    TARGET_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
  fi
else
  if [ "$(whoami)" == "root" ]; then
    TARGET_DIR="/etc/opt/chrome/native-messaging-hosts"
  else
    TARGET_DIR="$HOME/.config/google-chrome/NativeMessagingHosts"
  fi
fi

HOST_NAME=com.google.chrome.example.echo

# Create directory to store native messaging host.
mkdir -p "$TARGET_DIR"

# Copy native messaging host manifest.
cp "$DIR/$HOST_NAME.json" "$TARGET_DIR"

# Update host path in the manifest.
HOST_PATH="$DIR/native-messaging-example-host"
ESCAPED_HOST_PATH=${HOST_PATH////\\/}
sed -i -e "s/HOST_PATH/$ESCAPED_HOST_PATH/" "$TARGET_DIR/$HOST_NAME.json"

# Set permissions for the manifest so that all users can read it.
chmod o+r "$TARGET_DIR/$HOST_NAME.json"

echo Native messaging host $HOST_NAME has been installed.

Corrected nativeMessaging/host/uninstall_host.sh

#!/bin/sh
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

set -e

if [ $(uname -s) == 'Darwin' ]; then
  if [ "$(whoami)" == "root" ]; then
    TARGET_DIR="/Library/Google/Chrome/NativeMessagingHosts"
  else
    TARGET_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
  fi
else
  if [ "$(whoami)" == "root" ]; then
    TARGET_DIR="/etc/opt/chrome/native-messaging-hosts"
  else
    TARGET_DIR="$HOME/.config/google-chrome/NativeMessagingHosts"
  fi
fi

HOST_NAME=com.google.chrome.example.echo
rm "$TARGET_DIR/com.google.chrome.example.echo.json"
echo Native messaging host $HOST_NAME has been uninstalled.
HairOfTheDog
  • 2,489
  • 2
  • 29
  • 35
  • In 3.1. you give instructions "For OS X and Linux". 3.2 is "For OS X". Is it possible to do this on Windows? – DaveWalley Nov 06 '14 at 17:41
  • @DaveWalley The installation procedure for Windows is quite different from the install procedure for OS X and Linux. The Windows installation doesn't use scripts; instead it uses registry keys. For that reason you should be able to just skip to 3.3. NOTE: I have not tried this sample code on Windows so I cannot say for certain whether the installation instructions given by Google work correctly. If the Windows installation instructions work (or do not work) please leave a comment here so we can all benefit from your findings. – HairOfTheDog Nov 06 '14 at 19:21
  • I have followed directions for adding to registry, I have this in background.js: chrome.runtime.sendNativeMessage( 'ca.davidwalley.NativeMessagingHost' ,{ text: "Hello" } ,function(response){ console.log("Got "+response+"."); console.log(chrome.runtime.lastError); } ); I get the error messages: Got undefined. and Object {message: "Invalid native messaging host name specified."} – DaveWalley Nov 06 '14 at 21:28
  • Still not done, but I discovered that changing "ca.davidwalley.NativeMessagingHost" to all lower-case "ca.davidwalley.native_messaging_host" got me over one hurdle. – DaveWalley Nov 06 '14 at 21:59
  • @DaveWalley I tried the sample on Windows 7 and it worked for me without modification. I am running Chrome version 38.0.2125.122 m and Python version 2.7.8. I added the native messaging host in the registry under HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.google.chrome.example.echo as instructed in README.txt. Python.exe is on the system path env var. After installing Python I had to restart Chrome so Chrome could pick up the change to the PATH env var. After that it all worked. – HairOfTheDog Nov 11 '14 at 20:01
  • I got it working. You are correct that the larger problem is that Google has not provided comprehensive documentation. They seem to think they only need to document their half of the interface, and there are too many OSs for them to document the other half. Even though native messaging has no use outside of Google Chrome, and I would guess that example Hello World apps for say, 4 OSs, would cover 99% of developers, don't look to Google for help in this case. – DaveWalley Nov 12 '14 at 21:15
  • 1
    Thanks a lot for this. It helped. The lack of documentation is shocking. Also note that You have to install Python 2 instead of Python 3. It won't work with Python 3. The documentation neglects to state this. – CatNip44 Dec 04 '14 at 04:12
  • @HairOfTheDog any chance you still have the zip file? the link is now broken. – Compaq LE2202x Jul 22 '23 at 12:18
1

I would like to provide a python 3 version of the script to replace native-messaging-example-host. It is tested with Chrome v86 and works as expected. Note that python kernel crashes when tkinter window is closed - this is because of binary data exchange inside threading which causes thread to be hard locked (more info here). I added a command exit to be send from chrome app to stop thread's waiting for another stdin. After receiving it, python won't crash on exit.

Python 3 version (tested with 3.7.4):

# A simple native messaging host. Shows a Tkinter dialog with incoming messages
# that also allows to send message back to the webapp.

import struct
import sys
import threading
import queue as Queue

try:
  import tkinter as Tkinter
  import tkinter.messagebox
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.buffer.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.buffer.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.buffer.read(text_length).decode('utf-8')

    if text == '{"text":"exit"}':
      break

    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:
        tkinter.messagebox.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()

Disclaimer: I used 2to3 utility for initial conversion to python 3. I also adopted changes from a webextensions (firefox) version of the nativeMessage API example (it is simplified and not using tkinter gui).

Dima
  • 35
  • 5