├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── index.js ├── package.json ├── src ├── main.js └── rend.js └── test ├── main-to-rend ├── index.html ├── index.js └── rend.js └── rend-to-main ├── index.html ├── index.js └── rend.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.2.0 / 2016-03-19 2 | ------------------ 3 | - upgraded to `ipc-main`/`ipc-renderer` [#1][#1] 4 | 5 | 0.1.0 / 2015-07-11 6 | ------------------ 7 | - initial release 8 | 9 | 10 | [#1]: https://github.com/jprichardson/electron-ipc-stream/pull/1 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | electron-ipc-stream 2 | =================== 3 | 4 | Duplex stream that run over [Electron's IPC](https://github.com/atom/electron/tree/master/docs) mechanism. 5 | 6 | 7 | Why? 8 | --- 9 | 10 | This allows you to use any Node.js stream readable/writable and easily communicate between your 11 | main/renderer process. 12 | 13 | Since your `renderer` process is also responsible for UI/DOM, etc, you may not want to do any heavy 14 | processing on the renderer process. You could leverage this module to have the renderer stream 15 | data to the `main` process for processing and then the `main` module could stream results 16 | back to the `renderer` process for consumption. 17 | 18 | 19 | Install 20 | ------- 21 | 22 | npm i --save electron-ipc-stream 23 | 24 | 25 | Usage 26 | ----- 27 | 28 | ### Example 1: Pipe file from main process to renderer. 29 | 30 | **main.js:** 31 | 32 | ```js 33 | var app = require('app') 34 | var fs = require('fs') 35 | var path = require('path') 36 | var window = require('electron-window') 37 | var IPCStream = require('electron-ipc-stream') 38 | 39 | app.on('ready', function () { 40 | var win = window.createWindow({ height: 600, with: 1000 }) 41 | 42 | var ipcs = new IPCStream('any-arbitrary-channel-name', win) 43 | win.showUrl(path.resolve(__dirname, './index.html'), function () { 44 | // window is visible, dom is ready in window 45 | fs.createReadStream('/tmp/mainfile').pipe(ipcs) 46 | }) 47 | }) 48 | ``` 49 | 50 | **rend.js:** 51 | 52 | ```js 53 | var fs = require('fs') 54 | var ipc = require('ipc') 55 | var IPCStream = require('electron-ipc-stream') 56 | var ipcs = new IPCStream('any-arbitrary-channel-name') 57 | 58 | document.addEventListener('DOMContentLoaded', function () { 59 | ipcs.pipe(fs.createWriteStream('/tmp/rendfile')).on('finish', function () { 60 | console.log('done') 61 | }) 62 | }) 63 | ``` 64 | 65 | 66 | ### Example 2: Pipe file from renderer process to main. 67 | 68 | **main.js:** 69 | 70 | ```js 71 | var app = require('app') 72 | var fs = require('fs') 73 | var path = require('path') 74 | var window = require('electron-window') 75 | var IPCStream = require('electron-ipc-stream') 76 | 77 | var tmpfile = '/tmp/mainfile' 78 | app.on('ready', function () { 79 | var win = window.createWindow({ height: 600, with: 1000 }) 80 | var ipcs = new IPCStream('any-arbitrary-channel-name', win) 81 | ipcs.pipe(fs.createWriteStream(tmpfile)).on('finish', function () { 82 | console.log('done') 83 | }) 84 | win.showUrl(path.resolve(__dirname, './index.html'), function () { }) 85 | }) 86 | ``` 87 | 88 | **rend.js:** 89 | 90 | ```js 91 | var crypt = require('crypto') // notice this is 'crypt' and not 'crypto' 92 | var fs = require('fs') 93 | var ipc = require('ipc') 94 | var IPCStream = require('electron-ipc-stream') 95 | var ipcs = new IPCStream('any-arbitrary-channel-name') 96 | 97 | fs.writeFileSync('/tmp/rendfile', crypt.randomBytes(10000)) 98 | document.addEventListener('DOMContentLoaded', function () { 99 | fs.createReadStream(tmpfile).pipe(ipcs) 100 | }) 101 | ``` 102 | 103 | 104 | API 105 | ---- 106 | 107 | ### Main Process 108 | 109 | #### IPCStream(channel, [browserWindow], [streamOptions]) 110 | 111 | Create a new IPCStream in the `main` process. 112 | 113 | 114 | ### Renderer Process 115 | 116 | #### IPCStream(channel, [streamOptions]) 117 | 118 | Create a new IPCStream in the `renderer` process. 119 | 120 | 121 | ### Stream Options 122 | 123 | You shouldn't have to mess with `objectMode`. Under the hood, `objectMode` is `true`. 124 | Buffers are serialized to JSON. This is because of the way that Electron handles buffers 125 | in renderer. See: https://github.com/atom/electron/blob/master/docs/api/remote.md for 126 | more detail. You also may need to adjust [`highWaterMark`](https://nodejs.org/api/stream.html). 127 | 128 | 129 | ### JSON Objects 130 | 131 | It is completely safe to call `write` on either end of the stream with objects. 132 | 133 | source: 134 | 135 | ```js 136 | myStream.write({name: 'JP'}) 137 | ``` 138 | 139 | dest: 140 | 141 | ```js 142 | // streams 1 (flowing): 143 | myStream.on('data', function (data) { 144 | console.dir(data) // => {name: 'JP'} 145 | }) 146 | 147 | // streams 2/3 (pull, if you prefer): 148 | myStream.on('readable', function () { 149 | var data 150 | while (null !=== (data = myStream.read())) { 151 | console.dir(data) // => {name: 'JP'} 152 | } 153 | }) 154 | 155 | ``` 156 | 157 | 158 | 159 | ### Examples 160 | 161 | In the `./test` folder, you'll see two examples. You can run these by 162 | installing [electron-prebuilt](https://www.npmjs.com/package/electron-prebuilt): 163 | 164 | npm i -g electron-prebuilt 165 | electron ./test/main-to-rend 166 | electron ./test/rend-to-main 167 | 168 | 169 | 170 | License 171 | ------- 172 | 173 | MIT Copyright [JP Richardson](https://github.com/jprichardson) 174 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var isRenderer = require('is-electron-renderer') 2 | 3 | if (isRenderer) { 4 | module.exports = require('./src/rend') 5 | } else { 6 | module.exports = require('./src/main') 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-ipc-stream", 3 | "version": "0.2.0", 4 | "description": "Duplex stream over IPC for main/renderer to communicate with each other.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "standard" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/jprichardson/electron-ipc-stream.git" 12 | }, 13 | "keywords": [ 14 | "electron", 15 | "electron-component", 16 | "ipc", 17 | "stream", 18 | "streams", 19 | "duplex", 20 | "readable", 21 | "writeable" 22 | ], 23 | "author": "JP Richardson", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/jprichardson/electron-ipc-stream/issues" 27 | }, 28 | "homepage": "https://github.com/jprichardson/electron-ipc-stream#readme", 29 | "devDependencies": { 30 | "electron-window": "^0.6.2" 31 | }, 32 | "dependencies": { 33 | "buffer-json": "^1.0.0", 34 | "is-electron-renderer": "^2.0.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const bufferJson = require('buffer-json') 2 | const Duplex = require('stream').Duplex 3 | const ipcMain = require('electron').ipcMain 4 | const util = require('util') 5 | 6 | function MainIPCStream (channel, browserWindow, streamOpts) { 7 | if (!(this instanceof MainIPCStream)) { 8 | return new MainIPCStream(channel, browserWindow, streamOpts) 9 | } 10 | streamOpts = streamOpts || {} 11 | streamOpts.objectMode = streamOpts.objectMode ? streamOpts.objectMode : true 12 | 13 | this.browserWindow = browserWindow 14 | this.channel = channel 15 | 16 | const ipcCallback = (event, data) => { 17 | if (typeof data === 'string') { 18 | data = JSON.parse(data, bufferJson.reviver) 19 | } 20 | this.push(data) 21 | } 22 | ipcMain.on(this.channel, ipcCallback) 23 | 24 | this.on('finish', () => { 25 | if (this.browserWindow) this.browserWindow.webContents.send(this.channel + '-finish') 26 | ipcMain.removeListener(this.channel, ipcCallback) 27 | }) 28 | ipcMain.once(this.channel + '-finish', () => this.push(null)) 29 | 30 | Duplex.call(this, streamOpts) 31 | } 32 | util.inherits(MainIPCStream, Duplex) 33 | 34 | MainIPCStream.prototype._read = function () { } 35 | 36 | MainIPCStream.prototype._write = function (data, enc, next) { 37 | if (typeof data === 'string') { 38 | data = JSON.stringify(data) 39 | } 40 | if (Buffer.isBuffer(data)) { 41 | data = JSON.stringify(data, null, bufferJson.replacer) 42 | } 43 | if (!this.browserWindow) return console.warn('MainIPCStream: trying to write when no browserWindow is set.') 44 | this.browserWindow.webContents.send(this.channel, data) 45 | next() 46 | } 47 | 48 | module.exports = MainIPCStream 49 | -------------------------------------------------------------------------------- /src/rend.js: -------------------------------------------------------------------------------- 1 | const bufferJson = require('buffer-json') 2 | const Duplex = require('stream').Duplex 3 | const ipcRenderer = require('electron').ipcRenderer 4 | const util = require('util') 5 | 6 | function RendIPCStream (channel, streamOpts) { 7 | if (!(this instanceof RendIPCStream)) { 8 | return new RendIPCStream(channel, streamOpts) 9 | } 10 | streamOpts = streamOpts || {} 11 | streamOpts.objectMode = streamOpts.objectMode ? streamOpts.objectMode : true 12 | 13 | this.channel = channel 14 | 15 | const ipcCallback = (event, data) => { 16 | if (typeof data === 'string') { 17 | data = JSON.parse(data, bufferJson.reviver) 18 | } 19 | this.push(data) 20 | } 21 | ipcRenderer.on(this.channel, ipcCallback) 22 | 23 | this.on('finish', function () { 24 | ipcRenderer.send(this.channel + '-finish') 25 | ipcRenderer.removeListener(this.channel, ipcCallback) 26 | }) 27 | ipcRenderer.once(this.channel + '-finish', () => this.push(null)) 28 | 29 | Duplex.call(this, streamOpts) 30 | } 31 | util.inherits(RendIPCStream, Duplex) 32 | 33 | RendIPCStream.prototype._read = function () { } 34 | 35 | RendIPCStream.prototype._write = function (data, enc, next) { 36 | if (typeof data === 'string') { 37 | data = JSON.stringify(data) 38 | } 39 | if (Buffer.isBuffer(data)) { 40 | data = JSON.stringify(data, null, bufferJson.replacer) 41 | } 42 | 43 | ipcRenderer.send(this.channel, data) 44 | next() 45 | } 46 | 47 | module.exports = RendIPCStream 48 | -------------------------------------------------------------------------------- /test/main-to-rend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | hello 4 | -------------------------------------------------------------------------------- /test/main-to-rend/index.js: -------------------------------------------------------------------------------- 1 | var app = require('app') 2 | var assert = require('assert') 3 | var crypto = require('crypto') 4 | var fs = require('fs') 5 | var ipc = require('electron').ipcMain 6 | var path = require('path') 7 | var window = require('electron-window') 8 | var IPCStream = require('../../') 9 | 10 | var buffer = crypto.randomBytes(10000) 11 | var tmpfile = '/tmp/ipc-main' 12 | fs.writeFileSync(tmpfile, buffer) 13 | 14 | process.on('uncaughtException', function (err) { 15 | console.error(err) 16 | console.error(err.stack) 17 | app.quit() 18 | }) 19 | 20 | ipc.on('done', function () { 21 | var mainHash = md5File('/tmp/ipc-main') 22 | var rendHash = md5File('/tmp/ipc-rend') 23 | assert.strictEqual(mainHash, rendHash) 24 | 25 | console.log('') 26 | console.log(' success: ' + mainHash) 27 | console.log('') 28 | 29 | app.quit() 30 | }) 31 | 32 | app.on('ready', function () { 33 | var win = window.createWindow({ height: 600, with: 1000 }) 34 | 35 | var ipcs = new IPCStream('file', win) 36 | win._loadUrlWithArgs(path.resolve(__dirname, './index.html'), {}, function () { 37 | fs.createReadStream(tmpfile).pipe(ipcs) 38 | }) 39 | }) 40 | 41 | function md5File (file) { 42 | var data = fs.readFileSync(file) 43 | return crypto.createHash('md5').update(data).digest('hex') 44 | } 45 | -------------------------------------------------------------------------------- /test/main-to-rend/rend.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var ipc = require('ipc') 3 | var IPCStream = require('../../') 4 | var ipcs = new IPCStream('file') 5 | 6 | var tmpfile = '/tmp/ipc-rend' 7 | 8 | window.oneror = function (a, b, c, d, e) { 9 | console.error(e) 10 | console.error(e.stack) 11 | } 12 | 13 | document.addEventListener('DOMContentLoaded', function () { 14 | ipcs.pipe(fs.createWriteStream(tmpfile)).on('finish', function () { 15 | ipc.send('done') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /test/rend-to-main/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | hello 4 | -------------------------------------------------------------------------------- /test/rend-to-main/index.js: -------------------------------------------------------------------------------- 1 | var app = require('app') 2 | var assert = require('assert') 3 | var crypto = require('crypto') 4 | var fs = require('fs') 5 | var path = require('path') 6 | var window = require('electron-window') 7 | var IPCStream = require('../../') 8 | 9 | var tmpfile = '/tmp/ipc-main' 10 | 11 | process.on('uncaughtException', function (err) { 12 | console.error(err) 13 | console.error(err.stack) 14 | app.quit() 15 | }) 16 | 17 | function done () { 18 | var mainHash = md5File('/tmp/ipc-main') 19 | var rendHash = md5File('/tmp/ipc-rend') 20 | assert.strictEqual(mainHash, rendHash) 21 | 22 | console.log('') 23 | console.log(' success: ' + mainHash) 24 | console.log('') 25 | 26 | app.quit() 27 | } 28 | 29 | app.on('ready', function () { 30 | var win = window.createWindow({ height: 600, with: 1000 }) 31 | var ipcs = new IPCStream('file', win) 32 | ipcs.pipe(fs.createWriteStream(tmpfile)).on('finish', done) 33 | win._loadUrlWithArgs(path.resolve(__dirname, './index.html'), {}, function () { }) 34 | }) 35 | 36 | function md5File (file) { 37 | var data = fs.readFileSync(file) 38 | return crypto.createHash('md5').update(data).digest('hex') 39 | } 40 | -------------------------------------------------------------------------------- /test/rend-to-main/rend.js: -------------------------------------------------------------------------------- 1 | var crypt = require('crypto') 2 | var fs = require('fs') 3 | var IPCStream = require('../../') 4 | var ipcs = new IPCStream('file') 5 | 6 | var buffer = crypt.randomBytes(10000) 7 | var tmpfile = '/tmp/ipc-rend' 8 | fs.writeFileSync(tmpfile, buffer) 9 | 10 | window.oneror = function (a, b, c, d, e) { 11 | console.error(e) 12 | console.error(e.stack) 13 | } 14 | 15 | document.addEventListener('DOMContentLoaded', function () { 16 | fs.createReadStream(tmpfile).pipe(ipcs) 17 | }) 18 | --------------------------------------------------------------------------------