├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── chrome ├── background.js └── manifest.json ├── firefox ├── background.js └── manifest.json ├── nodejs ├── chrome │ └── native.messaging.example.json ├── example.js └── firefox │ └── native.messaging.example.json ├── package.json └── protocol.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | build/ 3 | coverage/ 4 | node_modules/ 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-present, Simeon Velichkov (https://github.com/simov/native-messaging) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # native-messaging 3 | 4 | [![npm-version]][npm] 5 | 6 | > Native Messaging Host Protocol for Browser Extensions ([Chrome][native-messaging], [Firefox][native-messaging-ff]) 7 | 8 | # API 9 | 10 | ```js 11 | #!/home/s/.nvm/versions/node/v8.11.2/bin/node 12 | 13 | // Might be good to use an explicit path to node on the shebang line 14 | // in case it isn't in PATH when launched by Chrome/Firefox 15 | 16 | var sendMessage = require('native-messaging')(handleMessage) 17 | 18 | function handleMessage (req) { 19 | if (req.message === 'ping') { 20 | sendMessage({message: 'pong', body: 'hello from nodejs app'}) 21 | } 22 | } 23 | ``` 24 | 25 | # Tips 26 | 27 | - `chrome.runtime.connectNative` returns a `Port` that can be used to establish persistent connection between the browser and the native app 28 | - the Port's `disconnect` method can be used to disconnect from the native app 29 | - the native app can use the OS's machinery to kill its own process at any time 30 | - `chrome.runtime.sendNativeMessage` can be used to send single message to the native app, useful with _non persistent background pages_ 31 | - single native app can communicate with multiple browser extensions 32 | - single browser extension can communicate with multiple native apps 33 | 34 | # Things to look for 35 | 36 | 1. Add `nativeMessaging` permission to the `manifest.json` file of your extension 37 | 2. Load the extension to get its ID generated 38 | 3. Put the `native.messaging.example.json` in `~/.config/google-chrome/NativeMessagingHosts/` or `~/.mozilla/native-messaging-hosts/` 39 | - The name of the file should be identical to the `name` field specified in that file 40 | - Only dots `.` are allowed as delimiters in the file name 41 | - The `path` key should be absolute path to your nodejs script `example.js` 42 | - Chrome - the `allowed_origins` key should contain the extension's ID loaded in step 2 43 | - Firefox - the `allowed_extensions` key should contain the extension's ID specified in its `manifest.json` file 44 | 4. Make sure that `example.js` is executable `chmod 755` 45 | 5. Make sure nodejs is reachable from the shebang line in `example.js` 46 | 6. Use the `name` specified in `native.messaging.example.json` to connect to the native app from the extension's background page `background.js` 47 | 48 | > Note that the install location for the `native.messaging.example.json` differs for each OS and browser. In case you plan to support multiple platforms, it's best to use install script. Here is an example of [such script][install]. 49 | 50 | # References 51 | 52 | Topic | Chrome | Firefox 53 | :-- | :--- | :--- 54 | Native Messaging Host Protocol | [link][native-messaging] | [link][native-messaging-ff] 55 | chrome.runtime.connectNative | [link][connect-native] | [link][connect-native-ff] 56 | Port | [link][port] | [link][port-ff] 57 | chrome.runtime.sendNativeMessage | [link][send-native-message] | [link][send-native-message-ff] 58 | host.json location | [link][host-location] | [link][host-location-ff] 59 | official example | [link][example] | [link][example-ff] 60 | 61 | 62 | [npm-version]: https://img.shields.io/npm/v/native-messaging.svg?style=flat-square (NPM Package Version) 63 | [npm]: https://www.npmjs.com/package/native-messaging 64 | 65 | [native-messaging]: https://developer.chrome.com/extensions/nativeMessaging 66 | [connect-native]: https://developer.chrome.com/extensions/runtime#method-connectNative 67 | [port]: https://developer.chrome.com/extensions/runtime#type-Port 68 | [send-native-message]: https://developer.chrome.com/extensions/runtime#method-sendNativeMessage 69 | [host-location]: https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host-location 70 | [example]: https://chromium.googlesource.com/chromium/src/+/master/chrome/common/extensions/docs/examples/api/nativeMessaging 71 | 72 | [native-messaging-ff]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging 73 | [connect-native-ff]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/connectNative 74 | [port-ff]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/Port 75 | [send-native-message-ff]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendNativeMessage 76 | [host-location-ff]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#Manifest_location 77 | [example-ff]: https://github.com/mdn/webextensions-examples/tree/master/native-messaging 78 | 79 | [background-pages]: https://developers.chrome.com/extensions/background_pages 80 | 81 | [install]: https://github.com/browserpass/browserpass/blob/master/install.sh 82 | 83 | [example-1]: https://github.com/flashlizi/node-chrome-bridge 84 | [example-2]: https://github.com/jdiamond/chrome-native-messaging 85 | -------------------------------------------------------------------------------- /chrome/background.js: -------------------------------------------------------------------------------- 1 | 2 | var port = chrome.runtime.connectNative('native.messaging.example') 3 | 4 | port.onMessage.addListener((req) => { 5 | if (chrome.runtime.lastError) { 6 | console.log(chrome.runtime.lastError.message) 7 | } 8 | handleMessage(req) 9 | }) 10 | 11 | port.onDisconnect.addListener(() => { 12 | if (chrome.runtime.lastError) { 13 | console.log(chrome.runtime.lastError.message) 14 | } 15 | console.log('Disconnected') 16 | }) 17 | 18 | port.postMessage({message: 'ping', body: 'hello from browser extension'}) 19 | 20 | function handleMessage (req) { 21 | if (req.message === 'pong') { 22 | console.log(req) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name" : "Native Messaging", 4 | "version" : "1.0.0", 5 | "description" : "Native Messaging", 6 | 7 | "background": { 8 | "persistent": true, 9 | "scripts": [ 10 | "background.js" 11 | ] 12 | }, 13 | 14 | "permissions": [ 15 | "nativeMessaging" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /firefox/background.js: -------------------------------------------------------------------------------- 1 | 2 | var port = chrome.runtime.connectNative('native.messaging.example') 3 | 4 | port.onMessage.addListener((req) => { 5 | if (chrome.runtime.lastError) { 6 | console.log(chrome.runtime.lastError.message) 7 | } 8 | handleMessage(req) 9 | }) 10 | 11 | port.onDisconnect.addListener(() => { 12 | if (chrome.runtime.lastError) { 13 | console.log(chrome.runtime.lastError.message) 14 | } 15 | console.log('Disconnected') 16 | }) 17 | 18 | port.postMessage({message: 'ping', body: 'hello from browser extension'}) 19 | 20 | function handleMessage (req) { 21 | if (req.message === 'pong') { 22 | console.log(req) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /firefox/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name" : "Native Messaging", 4 | "version" : "1.0.0", 5 | "description" : "Native Messaging", 6 | 7 | "background": { 8 | "persistent": true, 9 | "scripts": [ 10 | "background.js" 11 | ] 12 | }, 13 | 14 | "permissions": [ 15 | "nativeMessaging" 16 | ], 17 | 18 | "applications": { 19 | "gecko": { 20 | "id": "ping_pong@example.org", 21 | "strict_min_version": "57.0" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nodejs/chrome/native.messaging.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "native.messaging.example", 3 | "description": "Native Messaging Host Protocol Example", 4 | "path": "/home/s/projects/native-messaging/nodejs/example.js", 5 | "type": "stdio", 6 | "allowed_origins": [ 7 | "chrome-extension://naeffjobhembincnldgajdoaiegmclpb/" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /nodejs/example.js: -------------------------------------------------------------------------------- 1 | #!/home/s/.nvm/versions/node/v8.11.2/bin/node 2 | 3 | // Might be good to use an explicit path to node on the shebang line 4 | // in case it isn't in PATH when launched by Chrome 5 | 6 | var sendMessage = require('../protocol')(handleMessage) 7 | 8 | function handleMessage (req) { 9 | if (req.message === 'ping') { 10 | sendMessage({message: 'pong', body: 'hello from nodejs app'}) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /nodejs/firefox/native.messaging.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "native.messaging.example", 3 | "description": "Native Messaging Host Protocol Example", 4 | "path": "/home/s/projects/native-messaging/nodejs/example.js", 5 | "type": "stdio", 6 | "allowed_extensions": [ 7 | "ping_pong@example.org" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "native-messaging", 3 | "version": "1.0.0", 4 | "description": "Native Messaging Host Protocol for Browser Extensions", 5 | "keywords": [ 6 | "native", 7 | "messaging", 8 | "host", 9 | "protocol", 10 | "browser", 11 | "extension" 12 | ], 13 | "license": "MIT", 14 | "homepage": "https://github.com/simov/native-messaging", 15 | "author": "Simeon Velichkov (https://simov.github.io)", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/simov/native-messaging.git" 19 | }, 20 | "dependencies": {}, 21 | "devDependencies": {}, 22 | "bin": {}, 23 | "main": "./protocol.js", 24 | "files": [ 25 | "protocol.js", 26 | "LICENSE", 27 | "README.md", 28 | "package.json" 29 | ], 30 | "engines": { 31 | "node": ">=4.0.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /protocol.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (handleMessage) => { 3 | 4 | process.stdin.on('readable', () => { 5 | var input = [] 6 | var chunk 7 | while (chunk = process.stdin.read()) { 8 | input.push(chunk) 9 | } 10 | input = Buffer.concat(input) 11 | 12 | var msgLen = input.readUInt32LE(0) 13 | var dataLen = msgLen + 4 14 | 15 | if (input.length >= dataLen) { 16 | var content = input.slice(4, dataLen) 17 | var json = JSON.parse(content.toString()) 18 | handleMessage(json) 19 | } 20 | }) 21 | 22 | function sendMessage (msg) { 23 | var buffer = Buffer.from(JSON.stringify(msg)) 24 | 25 | var header = Buffer.alloc(4) 26 | header.writeUInt32LE(buffer.length, 0) 27 | 28 | var data = Buffer.concat([header, buffer]) 29 | process.stdout.write(data) 30 | } 31 | 32 | process.on('uncaughtException', (err) => { 33 | sendMessage({error: err.toString()}) 34 | }) 35 | 36 | return sendMessage 37 | 38 | } 39 | --------------------------------------------------------------------------------