diff --git a/README.md b/README.md index 9d6e8e5..35b8507 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,11 @@ Based on [Native Messaging on MDN](https://developer.mozilla.org/en-US/docs/Mozi If [`runtime.connectNative`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/connectNative) is used, `get_message()` must be called repeatedly in a loop to poll for messages. If [`runtime.sendNativeMessage`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendNativeMessage) is used, `get_message()` only needs to be called once. -## `encode_message( message_content )` -`nativemessaging.encode_message()` takes one argument, a message to be encoded. -Returns an encoded version of a message to be returned to the browser. Use with `send_message()`. +## `send_message( message )` +`nativemessaging.send_message()` takes one argument, a message to be returned to the browser. -## `send_message( encoded_message )` -`nativemessaging.send_message()` takes one argument, an encoded message from `encode_message()`. Returns a message to the browser. +## `install( browsers, manifest_file )` +`nativemessaging.install()` takes two arguments, a list of browsers to install the manifest and a manifest. Supported browsers are 'chrome' and 'firefox' and the manifest must be the contents of a valid manifest file. ## Sample Browser side: @@ -42,7 +41,7 @@ import nativemessaging while True: message = nativemessaging.get_message() if message == "hello": - nativemessaging.send_message(nativemessaging.encode_message("world")) + nativemessaging.send_message("world") ``` ## nativemessaging-install diff --git a/bin/nativemessaging-install.py b/bin/nativemessaging-install.py deleted file mode 100644 index 34dccd3..0000000 --- a/bin/nativemessaging-install.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import sys -import json -if sys.platform == "win32": - import winreg - -# locations for storing manifests -browser_info = { - "chrome": { - "registry": "Software\\Google\\Chrome\\NativeMessagingHosts", - "linux": os.path.join(os.path.expandvars("$HOME"), ".config/google-chrome/NativeMessagingHosts"), - "darwin": os.path.join(os.path.expandvars("$HOME"), "Library/Application Support/Google/Chrome/NativeMessagingHosts") - }, - "firefox": { - "registry": "Software\\Mozilla\\NativeMessagingHosts", - "linux": os.path.join(os.path.expandvars("$HOME"), ".mozilla/native-messaging-hosts"), - "darwin": os.path.join(os.path.expandvars("$HOME"), "Library/Application Support/Mozilla/NativeMessagingHosts") - } -} - - -def options(): - ap = argparse.ArgumentParser() - ap.add_argument("browser", choices=["chrome", "firefox"], nargs="+") - ap.add_argument("--manifest") - return vars(ap.parse_args()) - - -def readFile(file): - with open(file, "r") as f: - return f.read() - - -def writeFile(file, contents): - with open(file, "w") as f: - f.write(contents) - - -def createRegKey(path, value): - winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) - registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, winreg.KEY_WRITE) - winreg.SetValue(registry_key, "", winreg.REG_SZ, value) - winreg.CloseKey(registry_key) - print("Created registry key at HKEY_CURRENT_USER\\" + path) - - -def writeManifest(browser, path, manifest): - if browser == "firefox": - manifest.pop("allowed_origins", None) - elif browser == "chrome": - manifest.pop("allowed_extensions", None) - writeFile(path, json.dumps(manifest)) - print("Saved manifest file to " + path) - - -def main(): - opts = options() - if opts["manifest"] is not None and os.path.isfile(opts["manifest"]): # read from arguments if available - print("Reading manifest: " + opts["manifest"]) - file = readFile(opts["manifest"]) - elif os.path.isfile("native-manifest.json"): # fall back to native-manifest.json - print("Reading manifest: native-manifest.json") - file = readFile("native-manifest.json") - else: - raise Exception("No manifest found. Supply a manifest in the arguments, or create a manifest named native-manifest.json") - manifest = json.loads(file) - manifest["path"] = os.path.abspath(manifest["path"]) # ensure path is absolute - print("Absolute path: " + manifest["path"]) - if sys.platform == "win32": - install_dir = os.path.dirname(manifest["path"]) - if manifest["path"].endswith(".py"): # create batch file for python apps in windows - batch_path = os.path.join(install_dir, manifest["name"] + ".bat") - writeFile(batch_path, - "@echo off\npython -u \"{0}\"".format(manifest["path"])) - manifest["path"] = batch_path - print("Batch file created at: " + manifest["path"]) - # write registry key on windows - for browser in opts["browser"]: - manifest_path = os.path.join(install_dir, "{0}_{1}.json".format(manifest["name"], browser)) - writeManifest(browser, manifest_path, manifest) - createRegKey(os.path.join(browser_info[browser]["registry"], manifest["name"]), - manifest_path) - if sys.platform in ["linux", "darwin"]: # save manifest in linux and mac - for browser in opts["browser"]: - manifest_path = os.path.join(browser_info[browser][sys.platform], - manifest["name"] + ".json") - manifest_path_folder = os.path.dirname(manifest_path) - if not os.path.exists(manifest_path_folder): - os.mkdir(manifest_path_folder) - writeManifest(browser, manifest_path, manifest) - input("Done! Press enter or close the window.") - - -if __name__ == "__main__": - main() diff --git a/nativemessaging/__init__.py b/nativemessaging/__init__.py index f393651..7ec017d 100644 --- a/nativemessaging/__init__.py +++ b/nativemessaging/__init__.py @@ -1,28 +1,4 @@ -#!/usr/bin/env python -import json -import sys -import struct +# -*- coding: utf-8 -*- - -# from https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging -def get_message(): - raw_length = sys.stdin.buffer.read(4) - if len(raw_length) == 0: - sys.exit(0) - message_length = struct.unpack("@I", raw_length)[0] - message = sys.stdin.buffer.read(message_length).decode("utf-8") - return json.loads(message) - - -# Encode a message for transmission, given its content. -def encode_message(message_content): - encoded_content = json.dumps(message_content).encode("utf-8") - encoded_length = struct.pack("@I", len(encoded_content)) - return {"length": encoded_length, "content": encoded_content} - - -# Send an encoded message to stdout. -def send_message(encoded_message): - sys.stdout.buffer.write(encoded_message["length"]) - sys.stdout.buffer.write(encoded_message["content"]) - sys.stdout.buffer.flush() +from .nativemessaging import * +from .install import install diff --git a/nativemessaging/__main__.py b/nativemessaging/__main__.py new file mode 100644 index 0000000..86fe291 --- /dev/null +++ b/nativemessaging/__main__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from .install import main + +main() diff --git a/nativemessaging/install.py b/nativemessaging/install.py new file mode 100644 index 0000000..ba1bcdf --- /dev/null +++ b/nativemessaging/install.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +import argparse +import os +import sys +import json +if sys.platform == "win32": + import winreg + +# locations for storing manifests +browser_info = { + "chrome": { + "registry": "Software\\Google\\Chrome\\NativeMessagingHosts", + "linux": os.path.join(os.path.expandvars("$HOME"), ".config/google-chrome/NativeMessagingHosts"), + "darwin": os.path.join(os.path.expandvars("$HOME"), "Library/Application Support/Google/Chrome/NativeMessagingHosts") + }, + "firefox": { + "registry": "Software\\Mozilla\\NativeMessagingHosts", + "linux": os.path.join(os.path.expandvars("$HOME"), ".mozilla/native-messaging-hosts"), + "darwin": os.path.join(os.path.expandvars("$HOME"), "Library/Application Support/Mozilla/NativeMessagingHosts") + } +} + +def read_file(filename): + with open(filename, "r") as f: + return f.read() + + +def write_file(filename, contents): + with open(filename, "w") as f: + f.write(contents) + + +def write_manifest(browser, path, manifest): + if browser == "firefox": + manifest.pop("allowed_origins", None) + elif browser == "chrome": + manifest.pop("allowed_extensions", None) + write_file(path, json.dumps(manifest)) + #print("Saved manifest file to " + path) + + +def create_reg_key(path, value): + winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) + registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, winreg.KEY_WRITE) + winreg.SetValue(registry_key, "", winreg.REG_SZ, value) + winreg.CloseKey(registry_key) + print("Created registry key at HKEY_CURRENT_USER\\" + path) + + +def install_windows(browsers, manifest): + install_dir = os.path.dirname(manifest["path"]) + if manifest["path"].endswith(".py"): + # create batch file for python apps in windows + batch_path = os.path.join(install_dir, manifest["name"] + ".bat") + write_file(batch_path, "@echo off\npython -u \"{0}\"".format(manifest["path"])) + manifest["path"] = batch_path + #print("Batch file created at: " + manifest["path"]) + # write registry key on windows + for browser in browsers: + manifest_path = os.path.join(install_dir, "{0}_{1}.json".format(manifest["name"], browser)) + write_manifest(browser, manifest_path, manifest) + create_reg_key(os.path.join(browser_info[browser]["registry"], manifest["name"]), + manifest_path) + + +def install_unix(browsers, manifest): + for browser in browsers: + manifest_path_folder = browser_info[browser][sys.platform] + if not os.path.exists(manifest_path_folder): + os.mkdir(manifest_path_folder) + manifest_path = os.path.join(manifest_path_folder, manifest["name"] + ".json") + write_manifest(browser, manifest_path, manifest) + + +def install(browsers, manifest): + # ensure path is absolute + manifest["path"] = os.path.abspath(manifest["path"]) + #print("Absolute path: " + manifest["path"]) + + if sys.platform == "win32": + install_windows(browsers, manifest) + elif sys.platform in ["linux", "darwin"]: + install_unix(browsers, manifest) + + +def options(): + ap = argparse.ArgumentParser() + ap.add_argument("browser", choices=["chrome", "firefox"], nargs="+") + ap.add_argument("--manifest") + return vars(ap.parse_args()) + + +def main(): + opts = options() + + manifest_file = "native-manifest.json" + if opts["manifest"] is not None and os.path.isfile(opts["manifest"]): + # read from arguments if available + manifest_file = opts["manifest"] + elif os.path.isfile("native-manifest.json"): + # fall back to native-manifest.json + pass + else: + raise Exception("No manifest found. Supply a manifest in the arguments, or create a manifest named native-manifest.json") + + # read contents of manifest file + print("Reading manifest: " + manifest_file) + manifest_contents = read_file(manifest_file) + manifest = json.loads(manifest_contents) + + install(opts["browser"], manifest) + + input("Done! Press enter or close the window.") diff --git a/nativemessaging/nativemessaging.py b/nativemessaging/nativemessaging.py new file mode 100644 index 0000000..79437d9 --- /dev/null +++ b/nativemessaging/nativemessaging.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +import json +import sys +import struct + +def log_browser_console(message): + ''' + Log a message in the browser console + ''' + sys.stderr.write(message + '\n') + + +# from https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging +def get_message(): + ''' + Receive a native message from the browser + ''' + raw_length = sys.stdin.buffer.read(4) + if len(raw_length) == 0: + raise Exception("Message is empty") + message_length = struct.unpack("@I", raw_length)[0] + message = sys.stdin.buffer.read(message_length).decode("utf-8") + return json.loads(message) + + +def send_message(message): + ''' + Send a native message to the browser + ''' + encoded_content = json.dumps(message).encode("utf-8") + encoded_length = struct.pack("@I", len(encoded_content)) + sys.stdout.buffer.write(encoded_length) + sys.stdout.buffer.write(encoded_content) + sys.stdout.buffer.flush() diff --git a/setup.py b/setup.py index 40089da..c371c6a 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,9 @@ author_email="rayquaza01@outlook.com", license="MPL 2.0", packages=["nativemessaging"], - scripts=["bin/nativemessaging-install.py"], + entry_points={ + "console_scripts": ["nativemessaging-install = nativemessaging.install:main"] + }, include_package_data=True, package_data={"": ["README.md"]}, classifiers=[ diff --git a/test/example_message b/test/example_message new file mode 100644 index 0000000..426c401 Binary files /dev/null and b/test/example_message differ diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..8524a56 --- /dev/null +++ b/test/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +# -*- coding: utf-8 -*- + +import nativemessaging + +msg = nativemessaging.get_message() +nativemessaging.log_browser_console("received msg: %s" % str(msg)) +nativemessaging.send_message("test") + +print() diff --git a/test/test.sh b/test/test.sh new file mode 100755 index 0000000..1419fda --- /dev/null +++ b/test/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +PYTHONPATH=.. python test.py < example_message