├── .gitignore ├── README.md ├── app ├── background.js ├── main.css ├── main.html ├── main.js └── manifest.json ├── host ├── com.my_company.my_application-win.json ├── com.my_company.my_application.json ├── my_host.bat ├── my_host.js ├── node.exe ├── register.bat └── register.sh ├── index.js ├── json2msg.js ├── msg2json.js ├── package-lock.json ├── package.json ├── test.js ├── test.json └── test.msg /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chrome Native Messaging for Node.js 2 | 3 | Transform streams for writing Chrome App [native messaging][1] hosts in Node.js. 4 | 5 | [1]: https://developer.chrome.com/extensions/messaging#native-messaging 6 | 7 | ## Install 8 | 9 | ``` 10 | npm i -S chrome-native-messaging 11 | ``` 12 | 13 | ## API 14 | 15 | The module exports `Input`, `Output`, and `Transform` transform streams. 16 | 17 | `Input` streams transform bytes to objects. 18 | 19 | `Output` streams transform objects to bytes. 20 | 21 | Use `Transform` to easily create custom object-to-object transform streams. 22 | 23 | ``` 24 | var nativeMessage = require('chrome-native-messaging'); 25 | 26 | process.stdin 27 | .pipe(new nativeMessage.Input()) 28 | .pipe(new nativeMessage.Transform(function(msg, push, done) { 29 | var reply = getReplyFor(msg); // Implemented elsewhere by you. 30 | push(reply); // Push as many replies as you like. 31 | done(); // Call when done pushing replies. 32 | })) 33 | .pipe(new nativeMessage.Output()) 34 | .pipe(process.stdout) 35 | ; 36 | ``` 37 | 38 | ## Example 39 | 40 | The `app` directory contains a sample Chrome App. 41 | 42 | The `host` directory contains a native messaging host that you can send 43 | messages to. 44 | 45 | Go to the Chrome Extensions page (chrome://extensions/), hit "Load unpacked 46 | extension...", and select this project's `app` directory. 47 | 48 | SUPER IMPORTANT: Find the ID your app received when you loaded it and copy it 49 | to the host manifest in the `host` directory. 50 | 51 | SUPER IMPORTANT ON MACS: The path to the host must be absolute. Make sure the 52 | "path" property in the manifest is correct (it's probably not unless you're me). 53 | 54 | Install the host manifest: 55 | 56 | ``` 57 | sudo host/register.sh 58 | ``` 59 | 60 | On Windows (Run as Administrator): 61 | 62 | ``` 63 | host\register.bat 64 | ``` 65 | 66 | Open a new tab and hit Apps in the upper left. Launch the example app and send 67 | yourself messages. 68 | 69 | ## Testing 70 | 71 | Run `npm test` for the unit tests. 72 | 73 | `json2msg.js` is a script that can convert lines of JSON into native messages. 74 | Use it to send messages to your host to see how it responds. 75 | 76 | Pipe the output of your host to `msg2json.js` to see what its output looks like. 77 | 78 | ``` 79 | ./json2msg.js < test.json | ./host/my_host.js | ./msg2json.js 80 | ``` 81 | 82 | On Windows: 83 | 84 | ``` 85 | node json2msg.js < test.json | node host\my_host.js | node msg2json.js 86 | ``` 87 | 88 | ## Logging 89 | 90 | Enabling logging in Chrome can help find problems finding the manifest. 91 | 92 | Quit Chrome first. 93 | 94 | ``` 95 | open -a Google\ Chrome --args --enable-logging --v=1 96 | ``` 97 | 98 | On Windows: 99 | 100 | ``` 101 | start chrome --enable-logging --v=1 102 | ``` 103 | 104 | View the log like this: 105 | 106 | ``` 107 | less ~/Library/Application\ Support/Google/Chrome/chrome_debug.log 108 | ``` 109 | 110 | On Windows: 111 | 112 | ``` 113 | type "C:\Users\%USERNAME%\AppData\Local\Google\Chrome\User Data\chrome_debug.log" 114 | ``` 115 | 116 | More info: http://www.chromium.org/for-testers/enable-logging 117 | -------------------------------------------------------------------------------- /app/background.js: -------------------------------------------------------------------------------- 1 | chrome.app.runtime.onLaunched.addListener(function() { 2 | chrome.app.window.create('main.html', { 3 | bounds: { 4 | width: 640, 5 | height: 480 6 | } 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /app/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | overflow: auto; 3 | } 4 | -------------------------------------------------------------------------------- /app/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chrome Native Messaging Example 5 | 6 | 7 | 8 | 9 |

10 | 11 | 12 |

13 | 14 |
15 | 16 |

17 | Examples: 18 | 19 | 20 | 21 | 22 |

23 | 24 |

25 | 26 |

27 | 28 |
29 | 30 |

31 | 32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var application = 'com.my_company.my_application'; 4 | var port = null; 5 | 6 | document.getElementById('connect').addEventListener('click', function() { 7 | log('chrome.runtime.connectNative') 8 | 9 | port = chrome.runtime.connectNative(application); 10 | 11 | port.onMessage.addListener(log); 12 | 13 | port.onDisconnect.addListener(function(e) { 14 | log('unexpected disconnect'); 15 | 16 | port = null; 17 | }); 18 | }); 19 | 20 | document.getElementById('disconnect').addEventListener('click', function() { 21 | log('port.disconnect'); 22 | port.disconnect(); // this doesn't seem to trigger the onDisconnect event 23 | port = null; 24 | }); 25 | 26 | var examples = { 27 | ping: { ping: 'pong' }, 28 | readdir: { readdir: '/' }, 29 | subscribe: { subscribe: 'time' }, 30 | unsubscribe: { unsubscribe: 'time' } 31 | }; 32 | 33 | Array.prototype.slice.call(document.querySelectorAll('[data-example]')).forEach(function(example) { 34 | example.addEventListener('click', function() { 35 | document.getElementById('msg').value = JSON.stringify(examples[example.dataset.example]); 36 | }); 37 | }); 38 | 39 | document.getElementById('send').addEventListener('click', function() { 40 | var json = document.getElementById('msg').value; 41 | var msg; 42 | 43 | try { 44 | msg = JSON.parse(json); 45 | } catch (err) { 46 | return log('invalid JSON: ' + json); 47 | } 48 | 49 | if (port) { 50 | log('port.postMessage'); 51 | port.postMessage(msg); 52 | } else { 53 | log('chrome.runtime.sendNativeMessage'); 54 | chrome.runtime.sendNativeMessage(application, msg, log); 55 | } 56 | }); 57 | 58 | document.getElementById('clear').addEventListener('click', function() { 59 | document.getElementById('log').innerHTML = ''; 60 | }); 61 | 62 | function log(msg) { 63 | console.log(msg); 64 | 65 | var e = document.createElement('pre'); 66 | e.appendChild(document.createTextNode(typeof msg === 'object' ? JSON.stringify(msg) : msg)); 67 | document.getElementById('log').appendChild(e); 68 | } 69 | 70 | })(); 71 | -------------------------------------------------------------------------------- /app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvyGmcOkw4cTnhO0bgl3fQLAdv1jZp8T1ZHYI+4d8FgwwVKLYWE+pAuJ/0LrI69ibed4Nnnw5YleB1xCpI+mzB56xfXWboKp6lljevKqWJ5TpJk/Vam3kSSoZwpmJRXnzmcM3qKpL6viUhTfwGmQO6WVTsN4YCx+KWXv97IyF6yDTgd6hwFsvCZY2n1ADgurrQkE6AcJ3kK4xZ14jaHllXEdFcqwh0+Am5qLcIJ1cNo5iFD35exXsjwdQbmpt8sEk5f95pK5FEEbJFmOTguu2fOZycqIoTgoDrbbhT5k9TUogZaN5Lup0Iwh0Cv60i4C1f7IdPrxHuaYmYCfoUezXnQIDAQAB", 4 | "name": "Chrome Native Messaging Example", 5 | "version": "1.0", 6 | "description": "Send a message to a native application.", 7 | "app": { 8 | "background": { 9 | "scripts": [ "background.js" ] 10 | } 11 | }, 12 | "permissions": [ 13 | "nativeMessaging" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /host/com.my_company.my_application-win.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.my_company.my_application", 3 | "description": "My Application", 4 | "path": "my_host.bat", 5 | "type": "stdio", 6 | "allowed_origins": [ 7 | "chrome-extension://bmfbcejdknlknpncfpeloejonjoledha/" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /host/com.my_company.my_application.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.my_company.my_application", 3 | "description": "My Application", 4 | "path": "/Users/jason/code/chrome-native-messaging/host/my_host.js", 5 | "type": "stdio", 6 | "allowed_origins": [ 7 | "chrome-extension://bmfbcejdknlknpncfpeloejonjoledha/" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /host/my_host.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set LOG=C:\Users\Jason\Desktop\log.txt 4 | 5 | time /t >> %LOG% 6 | 7 | "%~dp0node.exe" "%~dp0my_host.js" %* 2>> %LOG% 8 | 9 | echo %errorlevel% >> %LOG% 10 | -------------------------------------------------------------------------------- /host/my_host.js: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/node 2 | 3 | // Might be good to use an explicit path to node on the shebang line in case 4 | // it isn't in PATH when launched by Chrome. 5 | 6 | var fs = require('fs'); 7 | 8 | var nativeMessage = require('../index'); 9 | 10 | var input = new nativeMessage.Input(); 11 | var transform = new nativeMessage.Transform(messageHandler); 12 | var output = new nativeMessage.Output(); 13 | 14 | process.stdin 15 | .pipe(input) 16 | .pipe(transform) 17 | .pipe(output) 18 | .pipe(process.stdout) 19 | ; 20 | 21 | var subscriptions = {}; 22 | 23 | var timer = setInterval(function() { 24 | if (subscriptions.time) { 25 | output.write({ time: new Date().toISOString() }); 26 | } 27 | }, 1000); 28 | 29 | input.on('end', function() { 30 | clearInterval(timer); 31 | }); 32 | 33 | function messageHandler(msg, push, done) { 34 | if (msg.readdir) { 35 | fs.readdir(msg.readdir, function(err, files) { 36 | if (err) { 37 | push({ error: err.message || err }); 38 | } else { 39 | files.forEach(function(file) { 40 | push({ file: file }); 41 | }); 42 | } 43 | 44 | done(); 45 | }); 46 | } else if (msg.subscribe) { 47 | subscriptions[msg.subscribe] = true; 48 | push({ subscribed: msg.subscribe }); 49 | done(); 50 | } else if (msg.unsubscribe) { 51 | delete subscriptions[msg.unsubscribe]; 52 | push({ unsubscribed: msg.unsubscribe }); 53 | done(); 54 | } else { 55 | // Just echo the message: 56 | push(msg); 57 | done(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /host/node.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdiamond/chrome-native-messaging/6859c7cba2b17d6898b05f69912bc0a507ff986d/host/node.exe -------------------------------------------------------------------------------- /host/register.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | call :isAdmin 4 | 5 | if %errorlevel% == 0 ( 6 | goto :run 7 | ) else ( 8 | echo Error: Run as administrator. 9 | ) 10 | 11 | exit /b 12 | 13 | :isAdmin 14 | fsutil dirty query %systemdrive% >nul 15 | exit /b 16 | 17 | :run 18 | 19 | reg add HKLM\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.my_company.my_application /f /ve /t REG_SZ /d %~dp0com.my_company.my_application-win.json 20 | 21 | pause 22 | -------------------------------------------------------------------------------- /host/register.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $0) 4 | 5 | mkdir -p /Library/Google/Chrome/NativeMessagingHosts 6 | cp com.my_company.my_application.json /Library/Google/Chrome/NativeMessagingHosts 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // chrome-native-messaging module 2 | // 3 | // Defines three Transform streams: 4 | // 5 | // - Input - transform native messages to JavaScript objects 6 | // - Output - transform JavaScript objects to native messages 7 | // - Transform - transform message objects to reply objects 8 | // - Debug - transform JavaScript objects to lines of JSON (for debugging, obviously) 9 | 10 | var stream = require('stream'); 11 | var util = require('util'); 12 | 13 | function Input() { 14 | stream.Transform.call(this); 15 | 16 | // Transform bytes... 17 | this._writableState.objectMode = false; 18 | // ...into objects. 19 | this._readableState.objectMode = true; 20 | 21 | // Unparsed data. 22 | this.buf = typeof Buffer.alloc === 'function' ? Buffer.alloc(0) : new Buffer(0); 23 | // Parsed length. 24 | this.len = null; 25 | } 26 | 27 | util.inherits(Input, stream.Transform); 28 | 29 | Input.prototype._transform = function(chunk, encoding, done) { 30 | // Save this chunk. 31 | this.buf = Buffer.concat([ this.buf, chunk ]); 32 | 33 | var self = this; 34 | 35 | function parseBuf() { 36 | // Do we have a length yet? 37 | if (typeof self.len !== 'number') { 38 | // Nope. Do we have enough bytes for the length? 39 | if (self.buf.length >= 4) { 40 | // Yep. Parse the bytes. 41 | self.len = self.buf.readUInt32LE(0); 42 | // Remove the length bytes from the buffer. 43 | self.buf = self.buf.slice(4); 44 | } 45 | } 46 | 47 | // Do we have a length yet? (We may have just parsed it.) 48 | if (typeof self.len === 'number') { 49 | // Yep. Do we have enough bytes for the message? 50 | if (self.buf.length >= self.len) { 51 | // Yep. Slice off the bytes we need. 52 | var message = self.buf.slice(0, self.len); 53 | // Remove the bytes for the message from the buffer. 54 | self.buf = self.buf.slice(self.len); 55 | // Clear the length so we know we need to parse it again. 56 | self.len = null; 57 | // Parse the message bytes. 58 | var obj = JSON.parse(message.toString()); 59 | // Enqueue it for reading. 60 | self.push(obj); 61 | // We could have more messages in the buffer so check again. 62 | parseBuf(); 63 | } 64 | } 65 | } 66 | 67 | // Check for a parsable buffer (both length and message). 68 | parseBuf(); 69 | 70 | // We're done. 71 | done(); 72 | }; 73 | 74 | function Output() { 75 | stream.Transform.call(this); 76 | 77 | this._writableState.objectMode = true; 78 | this._readableState.objectMode = false; 79 | } 80 | 81 | util.inherits(Output, stream.Transform); 82 | 83 | Output.prototype._transform = function(chunk, encoding, done) { 84 | var len = typeof Buffer.alloc === 'function' ? Buffer.alloc(4) : new Buffer(4); 85 | var buf = typeof Buffer.from === 'function' 86 | ? Buffer.from(JSON.stringify(chunk)) 87 | : new Buffer(JSON.stringify(chunk)); 88 | 89 | len.writeUInt32LE(buf.length, 0); 90 | 91 | this.push(Buffer.concat([len, buf])); 92 | 93 | done(); 94 | }; 95 | 96 | function Transform(handler) { 97 | stream.Transform.call(this); 98 | 99 | this._writableState.objectMode = true; 100 | this._readableState.objectMode = true; 101 | 102 | this.handler = handler; 103 | } 104 | 105 | util.inherits(Transform, stream.Transform); 106 | 107 | Transform.prototype._transform = function(msg, encoding, done) { 108 | this.handler(msg, this.push.bind(this), done); 109 | }; 110 | 111 | function Debug() { 112 | stream.Transform.call(this); 113 | 114 | this._writableState.objectMode = true; 115 | this._readableState.objectMode = false; 116 | } 117 | 118 | util.inherits(Debug, stream.Transform); 119 | 120 | Debug.prototype._transform = function(chunk, encoding, done) { 121 | this.push(JSON.stringify(chunk) + '\n'); 122 | 123 | done(); 124 | }; 125 | 126 | exports.Input = Input; 127 | exports.Output = Output; 128 | exports.Transform = Transform; 129 | exports.Debug = Debug; 130 | -------------------------------------------------------------------------------- /json2msg.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var readline = require('readline'); 5 | 6 | var nativeMessage = require('./index'); 7 | 8 | var output = new nativeMessage.Output(); 9 | 10 | readline.createInterface({ 11 | input: process.stdin, 12 | output: output, 13 | terminal: false 14 | }).on('line', function(line) { 15 | output.write(JSON.parse(line)); 16 | }); 17 | 18 | output.pipe(process.stdout); 19 | -------------------------------------------------------------------------------- /msg2json.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var nativeMessage = require('./index'); 4 | 5 | process.stdin 6 | .pipe(new nativeMessage.Input()) 7 | .pipe(new nativeMessage.Debug()) 8 | .pipe(process.stdout) 9 | ; 10 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-native-messaging", 3 | "version": "0.2.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 11 | "dev": true 12 | }, 13 | "brace-expansion": { 14 | "version": "1.1.11", 15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 16 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 17 | "dev": true, 18 | "requires": { 19 | "balanced-match": "1.0.0", 20 | "concat-map": "0.0.1" 21 | } 22 | }, 23 | "concat-map": { 24 | "version": "0.0.1", 25 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 26 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 27 | "dev": true 28 | }, 29 | "deep-equal": { 30 | "version": "1.1.1", 31 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", 32 | "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", 33 | "dev": true, 34 | "requires": { 35 | "is-arguments": "1.0.4", 36 | "is-date-object": "1.0.2", 37 | "is-regex": "1.0.5", 38 | "object-is": "1.0.2", 39 | "object-keys": "1.1.1", 40 | "regexp.prototype.flags": "1.3.0" 41 | } 42 | }, 43 | "define-properties": { 44 | "version": "1.1.3", 45 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 46 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 47 | "dev": true, 48 | "requires": { 49 | "object-keys": "1.1.1" 50 | } 51 | }, 52 | "defined": { 53 | "version": "1.0.0", 54 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 55 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 56 | "dev": true 57 | }, 58 | "dotignore": { 59 | "version": "0.1.2", 60 | "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", 61 | "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", 62 | "dev": true, 63 | "requires": { 64 | "minimatch": "3.0.4" 65 | } 66 | }, 67 | "es-abstract": { 68 | "version": "1.17.4", 69 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", 70 | "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", 71 | "dev": true, 72 | "requires": { 73 | "es-to-primitive": "1.2.1", 74 | "function-bind": "1.1.1", 75 | "has": "1.0.3", 76 | "has-symbols": "1.0.1", 77 | "is-callable": "1.1.5", 78 | "is-regex": "1.0.5", 79 | "object-inspect": "1.7.0", 80 | "object-keys": "1.1.1", 81 | "object.assign": "4.1.0", 82 | "string.prototype.trimleft": "2.1.1", 83 | "string.prototype.trimright": "2.1.1" 84 | } 85 | }, 86 | "es-to-primitive": { 87 | "version": "1.2.1", 88 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 89 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 90 | "dev": true, 91 | "requires": { 92 | "is-callable": "1.1.5", 93 | "is-date-object": "1.0.2", 94 | "is-symbol": "1.0.3" 95 | } 96 | }, 97 | "for-each": { 98 | "version": "0.3.3", 99 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 100 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 101 | "dev": true, 102 | "requires": { 103 | "is-callable": "1.1.5" 104 | } 105 | }, 106 | "fs.realpath": { 107 | "version": "1.0.0", 108 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 109 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 110 | "dev": true 111 | }, 112 | "function-bind": { 113 | "version": "1.1.1", 114 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 115 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 116 | "dev": true 117 | }, 118 | "glob": { 119 | "version": "7.1.6", 120 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 121 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 122 | "dev": true, 123 | "requires": { 124 | "fs.realpath": "1.0.0", 125 | "inflight": "1.0.6", 126 | "inherits": "2.0.4", 127 | "minimatch": "3.0.4", 128 | "once": "1.4.0", 129 | "path-is-absolute": "1.0.1" 130 | } 131 | }, 132 | "has": { 133 | "version": "1.0.3", 134 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 135 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 136 | "dev": true, 137 | "requires": { 138 | "function-bind": "1.1.1" 139 | } 140 | }, 141 | "has-symbols": { 142 | "version": "1.0.1", 143 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", 144 | "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", 145 | "dev": true 146 | }, 147 | "inflight": { 148 | "version": "1.0.6", 149 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 150 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 151 | "dev": true, 152 | "requires": { 153 | "once": "1.4.0", 154 | "wrappy": "1.0.2" 155 | } 156 | }, 157 | "inherits": { 158 | "version": "2.0.4", 159 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 160 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 161 | "dev": true 162 | }, 163 | "is-arguments": { 164 | "version": "1.0.4", 165 | "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", 166 | "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", 167 | "dev": true 168 | }, 169 | "is-callable": { 170 | "version": "1.1.5", 171 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", 172 | "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", 173 | "dev": true 174 | }, 175 | "is-date-object": { 176 | "version": "1.0.2", 177 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", 178 | "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", 179 | "dev": true 180 | }, 181 | "is-regex": { 182 | "version": "1.0.5", 183 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", 184 | "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", 185 | "dev": true, 186 | "requires": { 187 | "has": "1.0.3" 188 | } 189 | }, 190 | "is-symbol": { 191 | "version": "1.0.3", 192 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", 193 | "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", 194 | "dev": true, 195 | "requires": { 196 | "has-symbols": "1.0.1" 197 | } 198 | }, 199 | "minimatch": { 200 | "version": "3.0.4", 201 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 202 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 203 | "dev": true, 204 | "requires": { 205 | "brace-expansion": "1.1.11" 206 | } 207 | }, 208 | "minimist": { 209 | "version": "1.2.0", 210 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 211 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 212 | "dev": true 213 | }, 214 | "object-inspect": { 215 | "version": "1.7.0", 216 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", 217 | "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", 218 | "dev": true 219 | }, 220 | "object-is": { 221 | "version": "1.0.2", 222 | "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", 223 | "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", 224 | "dev": true 225 | }, 226 | "object-keys": { 227 | "version": "1.1.1", 228 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 229 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 230 | "dev": true 231 | }, 232 | "object.assign": { 233 | "version": "4.1.0", 234 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 235 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 236 | "dev": true, 237 | "requires": { 238 | "define-properties": "1.1.3", 239 | "function-bind": "1.1.1", 240 | "has-symbols": "1.0.1", 241 | "object-keys": "1.1.1" 242 | } 243 | }, 244 | "once": { 245 | "version": "1.4.0", 246 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 247 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 248 | "dev": true, 249 | "requires": { 250 | "wrappy": "1.0.2" 251 | } 252 | }, 253 | "path-is-absolute": { 254 | "version": "1.0.1", 255 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 256 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 257 | "dev": true 258 | }, 259 | "path-parse": { 260 | "version": "1.0.6", 261 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 262 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 263 | "dev": true 264 | }, 265 | "regexp.prototype.flags": { 266 | "version": "1.3.0", 267 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", 268 | "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", 269 | "dev": true, 270 | "requires": { 271 | "define-properties": "1.1.3", 272 | "es-abstract": "1.17.4" 273 | } 274 | }, 275 | "resolve": { 276 | "version": "1.14.2", 277 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", 278 | "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", 279 | "dev": true, 280 | "requires": { 281 | "path-parse": "1.0.6" 282 | } 283 | }, 284 | "resumer": { 285 | "version": "0.0.0", 286 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 287 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 288 | "dev": true, 289 | "requires": { 290 | "through": "2.3.8" 291 | } 292 | }, 293 | "string.prototype.trim": { 294 | "version": "1.2.1", 295 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", 296 | "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", 297 | "dev": true, 298 | "requires": { 299 | "define-properties": "1.1.3", 300 | "es-abstract": "1.17.4", 301 | "function-bind": "1.1.1" 302 | } 303 | }, 304 | "string.prototype.trimleft": { 305 | "version": "2.1.1", 306 | "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", 307 | "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", 308 | "dev": true, 309 | "requires": { 310 | "define-properties": "1.1.3", 311 | "function-bind": "1.1.1" 312 | } 313 | }, 314 | "string.prototype.trimright": { 315 | "version": "2.1.1", 316 | "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", 317 | "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", 318 | "dev": true, 319 | "requires": { 320 | "define-properties": "1.1.3", 321 | "function-bind": "1.1.1" 322 | } 323 | }, 324 | "tape": { 325 | "version": "4.13.0", 326 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.13.0.tgz", 327 | "integrity": "sha512-J/hvA+GJnuWJ0Sj8Z0dmu3JgMNU+MmusvkCT7+SN4/2TklW18FNCp/UuHIEhPZwHfy4sXfKYgC7kypKg4umbOw==", 328 | "dev": true, 329 | "requires": { 330 | "deep-equal": "1.1.1", 331 | "defined": "1.0.0", 332 | "dotignore": "0.1.2", 333 | "for-each": "0.3.3", 334 | "function-bind": "1.1.1", 335 | "glob": "7.1.6", 336 | "has": "1.0.3", 337 | "inherits": "2.0.4", 338 | "is-regex": "1.0.5", 339 | "minimist": "1.2.0", 340 | "object-inspect": "1.7.0", 341 | "resolve": "1.14.2", 342 | "resumer": "0.0.0", 343 | "string.prototype.trim": "1.2.1", 344 | "through": "2.3.8" 345 | } 346 | }, 347 | "through": { 348 | "version": "2.3.8", 349 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 350 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 351 | "dev": true 352 | }, 353 | "wrappy": { 354 | "version": "1.0.2", 355 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 356 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 357 | "dev": true 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-native-messaging", 3 | "version": "0.2.0", 4 | "author": "Jason Diamond ", 5 | "licenses": [ 6 | { 7 | "type": "MIT", 8 | "url": "http://jdiamond.mit-license.org/" 9 | } 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/jdiamond/chrome-native-messaging.git" 14 | }, 15 | "description": "Transform streams for writing Chrome App native messaging hosts in Node.js", 16 | "keywords": [ 17 | "chrome-app", 18 | "native-messaging" 19 | ], 20 | "main": "index.js", 21 | "dependencies": {}, 22 | "devDependencies": { 23 | "tape": "^4.13.0" 24 | }, 25 | "scripts": { 26 | "test": "node test.js" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var test = require('tape'); 4 | 5 | var nativeMessaging = require('./index'); 6 | 7 | test('Input', function(t) { 8 | var buf =typeof Buffer.alloc === 'function' ? Buffer.alloc(17) : new Buffer(17); 9 | buf.writeUInt32LE(13, 0); 10 | buf.write('{"foo":"bar"}', 4); 11 | 12 | var input = new nativeMessaging.Input(); 13 | 14 | input.once('readable', function() { 15 | var obj = input.read(); 16 | t.equal(obj.foo, 'bar'); 17 | t.end(); 18 | }); 19 | 20 | input.end(buf); 21 | }); 22 | 23 | test('Output', function(t) { 24 | var output = new nativeMessaging.Output(); 25 | 26 | output.once('readable', function() { 27 | var buf = output.read(); 28 | t.equal(buf.readUInt32LE(0), 13); 29 | t.equal(buf.slice(4).toString(), '{"foo":"bar"}'); 30 | t.end(); 31 | }); 32 | 33 | output.end({ foo: 'bar' }); 34 | }); 35 | 36 | test('Transform', function(t) { 37 | var transform = new nativeMessaging.Transform(function(msg, push, done) { 38 | push({ output: msg.input.toUpperCase() }); 39 | done(); 40 | }); 41 | 42 | transform.once('readable', function() { 43 | var obj = transform.read(); 44 | t.equal(obj.output, 'data'.toUpperCase()); 45 | t.end(); 46 | }); 47 | 48 | transform.end({ input: 'data' }); 49 | }); 50 | -------------------------------------------------------------------------------- /test.json: -------------------------------------------------------------------------------- 1 | { "msg": 1 } 2 | { "msg": 2 } 3 | { "readdir": "/" } 4 | { "msg": 3 } 5 | { "msg": 4 } 6 | -------------------------------------------------------------------------------- /test.msg: -------------------------------------------------------------------------------- 1 | {"msg":1} {"msg":2}{"readdir":"/"} {"msg":3} {"msg":4} --------------------------------------------------------------------------------