├── .npmrc
├── test
└── fixture
│ ├── package.json
│ ├── index.html
│ ├── util.js
│ ├── renderer.js
│ ├── test.js
│ └── index.js
├── .gitattributes
├── .gitignore
├── index.js
├── .editorconfig
├── .github
└── workflows
│ └── main.yml
├── source
├── util.js
├── renderer.js
└── main.js
├── license
├── package.json
├── index.test-d.ts
├── index.d.ts
└── readme.md
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/test/fixture/package.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn.lock
3 |
--------------------------------------------------------------------------------
/test/fixture/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (process.type === 'renderer') {
4 | module.exports.ipcRenderer = require('./source/renderer.js');
5 | } else {
6 | module.exports.ipcMain = require('./source/main.js');
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixture/util.js:
--------------------------------------------------------------------------------
1 | const countDataAndErrorListeners = emitter =>
2 | emitter.eventNames().filter(name => /(data|error)-channel/.test(name)).length;
3 |
4 | module.exports.countDataAndErrorListeners = countDataAndErrorListeners;
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.yml]
11 | indent_style = space
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | - push
4 | - pull_request
5 | jobs:
6 | test:
7 | name: Node.js ${{ matrix.node-version }}
8 | runs-on: macos-latest
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | node-version:
13 | - 16
14 | - 14
15 | steps:
16 | - uses: actions/checkout@v2
17 | - uses: actions/setup-node@v2
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | - run: npm install
21 | - run: npm test
22 |
--------------------------------------------------------------------------------
/source/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const getUniqueId = () => `${Date.now()}-${Math.random()}`;
4 |
5 | const getSendChannel = channel => `%better-ipc-send-channel-${channel}`;
6 | const getRendererSendChannel = channel => `%better-ipc-send-channel-${channel}`;
7 |
8 | module.exports.currentWindowChannel = '%better-ipc-current-window';
9 |
10 | module.exports.getSendChannel = getSendChannel;
11 | module.exports.getRendererSendChannel = getRendererSendChannel;
12 |
13 | module.exports.getResponseChannels = channel => {
14 | const id = getUniqueId();
15 | return {
16 | sendChannel: getSendChannel(channel),
17 | dataChannel: `%better-ipc-response-data-channel-${channel}-${id}`,
18 | errorChannel: `%better-ipc-response-error-channel-${channel}-${id}`
19 | };
20 | };
21 |
22 | module.exports.getRendererResponseChannels = channel => {
23 | const id = getUniqueId();
24 | return {
25 | sendChannel: getRendererSendChannel(channel),
26 | dataChannel: `%better-ipc-response-data-channel-${channel}-${id}`,
27 | errorChannel: `%better-ipc-response-error-channel-${channel}-${id}`
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-better-ipc",
3 | "version": "2.0.1",
4 | "description": "Simplified IPC communication for Electron apps",
5 | "license": "MIT",
6 | "repository": "sindresorhus/electron-better-ipc",
7 | "funding": "https://github.com/sponsors/sindresorhus",
8 | "author": {
9 | "name": "Sindre Sorhus",
10 | "email": "sindresorhus@gmail.com",
11 | "url": "https://sindresorhus.com"
12 | },
13 | "scripts": {
14 | "test": "xo && tsd",
15 | "test-with-app": "xo && (cd test/fixture && ava) && tsd"
16 | },
17 | "files": [
18 | "index.js",
19 | "index.d.ts",
20 | "source"
21 | ],
22 | "keywords": [
23 | "electron",
24 | "ipc",
25 | "communication",
26 | "communicate",
27 | "app",
28 | "message",
29 | "messages",
30 | "send",
31 | "receive",
32 | "call",
33 | "answer",
34 | "reply",
35 | "async",
36 | "await"
37 | ],
38 | "dependencies": {
39 | "serialize-error": "^8.1.0"
40 | },
41 | "devDependencies": {
42 | "ava": "^2.2.0",
43 | "electron": "^10",
44 | "execa": "^5.0.0",
45 | "tsd": "^0.14",
46 | "xo": "^0.39.1"
47 | },
48 | "xo": {
49 | "envs": [
50 | "node",
51 | "browser"
52 | ],
53 | "ignores": [
54 | "test/fixture"
55 | ]
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/source/renderer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const electron = require('electron');
3 | const {serializeError, deserializeError} = require('serialize-error');
4 | const util = require('./util.js');
5 |
6 | const {ipcRenderer} = electron;
7 | const ipc = Object.create(ipcRenderer || {});
8 |
9 | ipc.callMain = (channel, data) => new Promise((resolve, reject) => {
10 | const {sendChannel, dataChannel, errorChannel} = util.getResponseChannels(channel);
11 |
12 | const cleanup = () => {
13 | ipcRenderer.off(dataChannel, onData);
14 | ipcRenderer.off(errorChannel, onError);
15 | };
16 |
17 | const onData = (_event, result) => {
18 | cleanup();
19 | resolve(result);
20 | };
21 |
22 | const onError = (_event, error) => {
23 | cleanup();
24 | reject(deserializeError(error));
25 | };
26 |
27 | ipcRenderer.once(dataChannel, onData);
28 | ipcRenderer.once(errorChannel, onError);
29 |
30 | const completeData = {
31 | dataChannel,
32 | errorChannel,
33 | userData: data
34 | };
35 |
36 | ipcRenderer.send(sendChannel, completeData);
37 | });
38 |
39 | ipc.answerMain = (channel, callback) => {
40 | const sendChannel = util.getRendererSendChannel(channel);
41 |
42 | const listener = async (_event, data) => {
43 | const {dataChannel, errorChannel, userData} = data;
44 |
45 | try {
46 | ipcRenderer.send(dataChannel, await callback(userData));
47 | } catch (error) {
48 | ipcRenderer.send(errorChannel, serializeError(error));
49 | }
50 | };
51 |
52 | ipcRenderer.on(sendChannel, listener);
53 |
54 | return () => {
55 | ipcRenderer.off(sendChannel, listener);
56 | };
57 | };
58 |
59 | module.exports = ipc;
60 |
--------------------------------------------------------------------------------
/test/fixture/renderer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {ipcRenderer} = require('electron');
3 | const {ipcRenderer: ipc} = require('../..');
4 | const {countDataAndErrorListeners} = require('./util');
5 |
6 | ipcRenderer.once('count', () => ipcRenderer.send('log', 'test-count-renderer-listeners: ' + countDataAndErrorListeners(ipcRenderer)));
7 |
8 | ipc.callMain('test', 'optional-data').then(answer => {
9 | ipcRenderer.send('log', 'test:renderer:answer-from-main: ' + answer);
10 | });
11 |
12 | ipc.answerMain('test', data => {
13 | ipcRenderer.send('log', 'test:renderer:data-from-main: ' + data);
14 | return 'test:renderer:answer-data';
15 | });
16 |
17 | ipc.callMain('test-error').catch(error => {
18 | ipcRenderer.send('log', 'test-error:renderer:from-main:is-error ' + (error instanceof Error));
19 | ipcRenderer.send('log', 'test-error:renderer:from-main:error-message ' + error.message);
20 | });
21 |
22 | ipc.callMain('test-focused', 'optional-data').then(answer => {
23 | ipcRenderer.send('log', 'test-focused:renderer:answer-from-main: ' + answer);
24 | });
25 |
26 | ipc.callMain('test-concurrency', 'data-1').then(answer => {
27 | ipcRenderer.send('log', 'test-concurrency:renderer:answer-from-main-1: ' + answer);
28 | });
29 |
30 | ipc.callMain('test-concurrency', 'data-2').then(answer => {
31 | ipcRenderer.send('log', 'test-concurrency:renderer:answer-from-main-2: ' + answer);
32 | });
33 |
34 | ipc.answerMain('test-focused', data => {
35 | ipcRenderer.send('log', 'test-focused:renderer:data-from-main: ' + data);
36 | return 'test-focused:renderer:answer-data';
37 | });
38 |
39 | ipc.callMain('test-specific-window', 'data-1').then(answer => {
40 | ipcRenderer.send('log', 'test-specific-window:renderer:answer-from-main: ' + answer);
41 | });
42 |
--------------------------------------------------------------------------------
/test/fixture/test.js:
--------------------------------------------------------------------------------
1 | import electron from 'electron';
2 | import test from 'ava';
3 | import execa from 'execa';
4 |
5 | const run = async file => {
6 | const {stdout} = await execa(electron, [file], {
7 | timeout: 10000
8 | });
9 |
10 | return stdout.trim();
11 | };
12 |
13 | test('main', async t => {
14 | const stdout = await run('index.js');
15 |
16 | const logs = [
17 | ...stdout.split('\n')
18 | ].filter(x =>
19 | x !== ''
20 | ).sort();
21 |
22 | console.log(logs);
23 |
24 | t.deepEqual(logs, [
25 | 'test-concurrency:main:data-from-renderer: data-1',
26 | 'test-concurrency:main:data-from-renderer: data-2',
27 | 'test-concurrency:renderer:answer-from-main-1: test-concurrency:main:answer:data-1',
28 | 'test-concurrency:renderer:answer-from-main-2: test-concurrency:main:answer:data-2',
29 | 'test-count-main-listeners: 0',
30 | 'test-count-renderer-listeners: 0',
31 | 'test-error:renderer:from-main:error-message test-error:main:answer',
32 | 'test-error:renderer:from-main:is-error true',
33 | 'test-focused:main:answer-from-renderer: test-focused:renderer:answer-data',
34 | 'test-focused:main:data-from-renderer: optional-data',
35 | 'test-focused:main:error-from-renderer: No browser window in focus',
36 | 'test-focused:renderer:answer-from-main: test-focused:main:answer',
37 | 'test-focused:renderer:data-from-main: optional-data',
38 | 'test-specific-window:main:data-from-renderer: data-1',
39 | 'test-specific-window:renderer:answer-from-main: test-specific-window:main:answer:data-1',
40 | 'test:main:answer-from-renderer: test:renderer:answer-data',
41 | 'test:main:data-from-renderer: optional-data',
42 | 'test:main:error-from-renderer: Browser window required',
43 | 'test:renderer:answer-from-main: test:main:answer',
44 | 'test:renderer:data-from-main: optional-data'
45 | ]);
46 | });
47 |
--------------------------------------------------------------------------------
/test/fixture/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const path = require('path');
3 | const {app, BrowserWindow, ipcMain} = require('electron');
4 | const {ipcMain: ipc} = require('../../index.js');
5 | const {countDataAndErrorListeners} = require('./util.js');
6 |
7 | let countOfLogs = 0;
8 |
9 | ipcMain.on('log', (_event, log) => {
10 | console.log(log);
11 | countOfLogs++;
12 | });
13 |
14 | ipc.answerRenderer('test', async data => {
15 | console.log('test:main:data-from-renderer:', data);
16 | return 'test:main:answer';
17 | });
18 |
19 | ipc.answerRenderer('test-focused', async data => {
20 | console.log('test-focused:main:data-from-renderer:', data);
21 | return 'test-focused:main:answer';
22 | });
23 |
24 | ipc.answerRenderer('test-error', async () => {
25 | throw new Error('test-error:main:answer');
26 | });
27 |
28 | ipc.answerRenderer('test-concurrency', async data => {
29 | console.log('test-concurrency:main:data-from-renderer:', data);
30 | return `test-concurrency:main:answer:${data}`;
31 | });
32 |
33 | (async () => {
34 | await app.whenReady();
35 |
36 | const mainWindow = new BrowserWindow({
37 | webPreferences: {
38 | nodeIntegration: true
39 | }
40 | });
41 |
42 | ipc.answerRenderer(mainWindow, 'test-specific-window', async data => {
43 | console.log('test-specific-window:main:data-from-renderer:', data);
44 | return `test-specific-window:main:answer:${data}`;
45 | });
46 |
47 | await mainWindow.loadFile(path.join(__dirname, 'index.html'));
48 |
49 | const answer = await ipc.callRenderer(mainWindow, 'test', 'optional-data');
50 | console.log('test:main:answer-from-renderer:', answer);
51 |
52 | const answerFromFocusedRenderer = await ipc.callFocusedRenderer('test-focused', 'optional-data');
53 | console.log('test-focused:main:answer-from-renderer:', answerFromFocusedRenderer);
54 |
55 | try {
56 | await ipc.callRenderer();
57 | } catch (error) {
58 | console.log('test:main:error-from-renderer:', error.message);
59 | }
60 |
61 | try {
62 | mainWindow.blur();
63 | mainWindow.hide();
64 | await ipc.callFocusedRenderer();
65 | } catch (error) {
66 | console.log('test-focused:main:error-from-renderer:', error.message);
67 | }
68 |
69 | // Get the count of listeners from the renderer.
70 | mainWindow.webContents.send('count');
71 |
72 | console.log('test-count-main-listeners:', countDataAndErrorListeners(ipcMain));
73 |
74 | // Wait to get all logs from the renderer and then quit the app.
75 | setInterval(() => {
76 | if (countOfLogs === 10) {
77 | app.quit();
78 | }
79 | }, 100);
80 | })();
81 |
--------------------------------------------------------------------------------
/index.test-d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import {expectType, expectError} from 'tsd';
3 | import {BrowserWindow} from 'electron';
4 | import {ipcMain, ipcRenderer} from './index.js';
5 |
6 | const browserWindow = BrowserWindow.getFocusedWindow()!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
7 |
8 | // IpcMain
9 |
10 | expectType>(
11 | ipcMain.callRenderer(browserWindow, 'get-emoji')
12 | );
13 | expectType>(
14 | ipcMain.callRenderer(browserWindow, 'get-emoji', 'unicorn')
15 | );
16 | expectType>(
17 | ipcMain.callRenderer(browserWindow, 'get-emoji', 'unicorn')
18 | );
19 | expectType>(
20 | ipcMain.callRenderer(browserWindow, 'get-emoji', 'unicorn')
21 | );
22 | expectType>(
23 | ipcMain.callRenderer(browserWindow, 'get-emoji', 'unicorn')
24 | );
25 |
26 | const detachListener = ipcMain.answerRenderer('get-emoji', emojiName => {
27 | expectType(emojiName);
28 | return '🦄';
29 | });
30 | ipcMain.answerRenderer('get-emoji', async emojiName => {
31 | expectType(emojiName);
32 | return '🦄';
33 | });
34 | ipcMain.answerRenderer('get-emoji', async emojiName => {
35 | expectType(emojiName);
36 | return '🦄';
37 | });
38 | ipcMain.answerRenderer('get-emoji', async emojiName => {
39 | expectType(emojiName);
40 | return '🦄';
41 | });
42 | ipcMain.answerRenderer(browserWindow, 'get-emoji', async emojiName => {
43 | expectType(emojiName);
44 | return '🦄';
45 | });
46 |
47 | expectType<() => void>(detachListener);
48 | detachListener();
49 |
50 | ipcMain.sendToRenderers('get-emoji');
51 | ipcMain.sendToRenderers('get-emoji', '🦄');
52 | ipcMain.sendToRenderers('get-emoji', '🦄');
53 |
54 | expectError(ipcMain.callMain);
55 |
56 | // IpcRenderer
57 |
58 | expectType>(
59 | ipcRenderer.callMain('get-emoji', 'unicorn')
60 | );
61 | expectType>(
62 | ipcRenderer.callMain('get-emoji', 'unicorn')
63 | );
64 | expectType>(
65 | ipcRenderer.callMain('get-emoji', 'unicorn')
66 | );
67 |
68 | const detachListener2 = ipcRenderer.answerMain(
69 | 'get-emoji',
70 | async emojiName => {
71 | expectType(emojiName);
72 | return '🦄';
73 | }
74 | );
75 | ipcRenderer.answerMain('get-emoji', emojiName => {
76 | expectType(emojiName);
77 | return '🦄';
78 | });
79 | ipcRenderer.answerMain('get-emoji', emojiName => {
80 | expectType(emojiName);
81 | return '🦄';
82 | });
83 | ipcRenderer.answerMain('get-emoji', emojiName => {
84 | expectType(emojiName);
85 | return '🦄';
86 | });
87 |
88 | expectType<() => void>(detachListener2);
89 | detachListener();
90 |
91 | expectError(ipcRenderer.callRenderer);
92 |
--------------------------------------------------------------------------------
/source/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const electron = require('electron');
3 | const {serializeError, deserializeError} = require('serialize-error');
4 | const util = require('./util.js');
5 |
6 | const {ipcMain, BrowserWindow} = electron;
7 | const ipc = Object.create(ipcMain || {});
8 |
9 | ipc.callRenderer = (browserWindow, channel, data) => new Promise((resolve, reject) => {
10 | if (!browserWindow) {
11 | throw new Error('Browser window required');
12 | }
13 |
14 | const {sendChannel, dataChannel, errorChannel} = util.getRendererResponseChannels(channel);
15 |
16 | const cleanup = () => {
17 | ipcMain.off(dataChannel, onData);
18 | ipcMain.off(errorChannel, onError);
19 | };
20 |
21 | const onData = (event, result) => {
22 | const window = BrowserWindow.fromWebContents(event.sender);
23 | if (window.id === browserWindow.id) {
24 | cleanup();
25 | resolve(result);
26 | }
27 | };
28 |
29 | const onError = (event, error) => {
30 | const window = BrowserWindow.fromWebContents(event.sender);
31 | if (window.id === browserWindow.id) {
32 | cleanup();
33 | reject(deserializeError(error));
34 | }
35 | };
36 |
37 | ipcMain.on(dataChannel, onData);
38 | ipcMain.on(errorChannel, onError);
39 |
40 | const completeData = {
41 | dataChannel,
42 | errorChannel,
43 | userData: data
44 | };
45 |
46 | if (browserWindow.webContents) {
47 | browserWindow.webContents.send(sendChannel, completeData);
48 | }
49 | });
50 |
51 | ipc.callFocusedRenderer = async (...args) => {
52 | const focusedWindow = BrowserWindow.getFocusedWindow();
53 | if (!focusedWindow) {
54 | throw new Error('No browser window in focus');
55 | }
56 |
57 | return ipc.callRenderer(focusedWindow, ...args);
58 | };
59 |
60 | ipc.answerRenderer = (browserWindowOrChannel, channelOrCallback, callbackOrNothing) => {
61 | let window;
62 | let channel;
63 | let callback;
64 |
65 | if (callbackOrNothing === undefined) {
66 | channel = browserWindowOrChannel;
67 | callback = channelOrCallback;
68 | } else {
69 | window = browserWindowOrChannel;
70 | channel = channelOrCallback;
71 | callback = callbackOrNothing;
72 |
73 | if (!window) {
74 | throw new Error('Browser window required');
75 | }
76 | }
77 |
78 | const sendChannel = util.getSendChannel(channel);
79 |
80 | const listener = async (event, data) => {
81 | const browserWindow = BrowserWindow.fromWebContents(event.sender);
82 |
83 | if (window && window.id !== browserWindow.id) {
84 | return;
85 | }
86 |
87 | const send = (channel, data) => {
88 | if (!(browserWindow && browserWindow.isDestroyed())) {
89 | event.sender.send(channel, data);
90 | }
91 | };
92 |
93 | const {dataChannel, errorChannel, userData} = data;
94 |
95 | try {
96 | send(dataChannel, await callback(userData, browserWindow));
97 | } catch (error) {
98 | send(errorChannel, serializeError(error));
99 | }
100 | };
101 |
102 | ipcMain.on(sendChannel, listener);
103 |
104 | return () => {
105 | ipcMain.off(sendChannel, listener);
106 | };
107 | };
108 |
109 | ipc.sendToRenderers = (channel, data) => {
110 | for (const browserWindow of BrowserWindow.getAllWindows()) {
111 | if (browserWindow.webContents) {
112 | browserWindow.webContents.send(channel, data);
113 | }
114 | }
115 | };
116 |
117 | module.exports = ipc;
118 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import {BrowserWindow, IpcMain, IpcRenderer} from 'electron';
2 |
3 | export interface MainProcessIpc extends IpcMain {
4 | /**
5 | Send a message to the given window.
6 |
7 | In the renderer process, use `ipcRenderer.answerMain` to reply to this message.
8 |
9 | @param browserWindow - The window to send the message to.
10 | @param channel - The channel to send the message on.
11 | @param data - The data to send to the receiver.
12 | @returns - The reply from the renderer process.
13 |
14 | @example
15 | ```
16 | import {BrowserWindow} from 'electron';
17 | import {ipcMain as ipc} from 'electron-better-ipc';
18 |
19 | const browserWindow = BrowserWindow.getFocusedWindow();
20 |
21 | const emoji = await ipc.callRenderer(browserWindow!, 'get-emoji', 'unicorn');
22 | console.log(emoji);
23 | //=> '🦄'
24 | ```
25 | */
26 | callRenderer(
27 | browserWindow: BrowserWindow,
28 | channel: string,
29 | data?: DataType
30 | ): Promise;
31 |
32 | /**
33 | Send a message to the focused window, as determined by `electron.BrowserWindow.getFocusedWindow`.
34 |
35 | In the renderer process, use `ipcRenderer.answerMain` to reply to this message.
36 |
37 | @param channel - The channel to send the message on.
38 | @param data - The data to send to the receiver.
39 | @returns - The reply from the renderer process.
40 |
41 | @example
42 | ```
43 | import {ipcMain as ipc} from 'electron-better-ipc';
44 |
45 | const emoji = await ipc.callFocusedRenderer('get-emoji', 'unicorn');
46 | console.log(emoji);
47 | //=> '🦄'
48 | ```
49 | */
50 | callFocusedRenderer(
51 | channel: string,
52 | data?: DataType
53 | ): Promise;
54 |
55 | /**
56 | This method listens for a message from `ipcRenderer.callMain` defined in a renderer process and replies back.
57 |
58 | @param channel - The channel to send the message on.
59 | @param callback - The return value is sent back to the `ipcRenderer.callMain` in the renderer process.
60 | @returns A function, that when called, removes the listener.
61 |
62 | @example
63 | ```
64 | import {ipcMain as ipc} from 'electron-better-ipc';
65 |
66 | ipc.answerRenderer('get-emoji', async emojiName => {
67 | const emoji = await getEmoji(emojiName);
68 | return emoji;
69 | });
70 | ```
71 | */
72 | answerRenderer(
73 | channel: string,
74 | callback: (
75 | data: DataType,
76 | browserWindow: BrowserWindow
77 | ) => ReturnType | PromiseLike
78 | ): () => void;
79 |
80 | /**
81 | This method listens for a message from `ipcRenderer.callMain` defined in the given BrowserWindow's renderer process and replies back.
82 |
83 | @param browserWindow - The window for which to expect the message.
84 | @param channel - The channel to send the message on.
85 | @param callback - The return value is sent back to the `ipcRenderer.callMain` in the renderer process.
86 | @returns A function, that when called, removes the listener.
87 |
88 | @example
89 | ```
90 | import {ipcMain as ipc} from 'electron-better-ipc';
91 |
92 | ipc.answerRenderer('get-emoji', async emojiName => {
93 | const emoji = await getEmoji(emojiName);
94 | return emoji;
95 | });
96 | ```
97 | */
98 | answerRenderer(
99 | browserWindow: BrowserWindow,
100 | channel: string,
101 | callback: (
102 | data: DataType,
103 | browserWindow: BrowserWindow
104 | ) => ReturnType | PromiseLike
105 | ): () => void;
106 |
107 | /**
108 | Send a message to all renderer processes (windows).
109 |
110 | @param channel - The channel to send the message on.
111 | @param data - The data to send to the receiver.
112 | */
113 | sendToRenderers(channel: string, data?: DataType): void;
114 | }
115 |
116 | export interface RendererProcessIpc extends IpcRenderer {
117 | /**
118 | Send a message to the main process.
119 |
120 | In the main process, use `ipcMain.answerRenderer` to reply to this message.
121 |
122 | @param channel - The channel to send the message on.
123 | @param data - The data to send to the receiver.
124 | @returns The reply from the main process.
125 |
126 | @example
127 | ```
128 | import {ipcRenderer as ipc} from 'electron-better-ipc';
129 |
130 | const emoji = await ipc.callMain('get-emoji', 'unicorn');
131 | console.log(emoji);
132 | //=> '🦄'
133 | ```
134 | */
135 | callMain(channel: string, data?: DataType): Promise;
136 |
137 | /**
138 | This method listens for a message from `ipcMain.callRenderer` defined in the main process and replies back.
139 |
140 | @param channel - The channel to send the message on.
141 | @param callback - The return value is sent back to the `ipcMain.callRenderer` in the main process.
142 | @returns A function, that when called, removes the listener.
143 |
144 | @example
145 | ```
146 | import {ipcRenderer as ipc} from 'electron-better-ipc';
147 |
148 | ipc.answerMain('get-emoji', async emojiName => {
149 | const emoji = await getEmoji(emojiName);
150 | return emoji;
151 | });
152 | ```
153 | */
154 | answerMain(
155 | channel: string,
156 | callback: (data: DataType) => ReturnType | PromiseLike
157 | ): () => void;
158 | }
159 |
160 | export const ipcMain: MainProcessIpc;
161 | export const ipcRenderer: RendererProcessIpc;
162 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # electron-better-ipc
2 |
3 | > Simplified IPC communication for Electron apps
4 |
5 | The biggest benefit of this module over the [built-in IPC](https://electronjs.org/docs/api/ipc-main) is that it enables you to send a message and get the response back in the same call. This would usually require multiple IPC subscriptions.
6 |
7 | You can use this module directly in both the main and renderer process.
8 |
9 | ## Install
10 |
11 | ```sh
12 | npm install electron-better-ipc
13 | ```
14 |
15 | *Requires Electron 10 or later.*
16 |
17 | ## Usage
18 |
19 | ### Using the built-in IPC
20 |
21 | Here, as an example, we use the built-in IPC to get an emoji by name in the renderer process from the main process. Notice how it requires coordinating multiple IPC subscriptions.
22 |
23 | ###### Main
24 |
25 | ```js
26 | const {ipcMain: ipc} = require('electron');
27 |
28 | ipc.on('get-emoji', async (event, emojiName) => {
29 | const emoji = await getEmoji(emojiName);
30 | event.sender.send('get-emoji-response', emoji);
31 | });
32 | ```
33 |
34 | ###### Renderer
35 |
36 | ```js
37 | const {ipcRenderer: ipc} = require('electron');
38 |
39 | ipc.on('get-emoji-response', (event, emoji) => {
40 | console.log(emoji);
41 | //=> '🦄'
42 | });
43 |
44 | ipc.send('get-emoji', 'unicorn');
45 | ```
46 |
47 | ### Using this module
48 |
49 | As you can see below, this module makes it much simpler to handle the communication. You no longer need multiple IPC subscriptions and you can just `await` the response in the same call.
50 |
51 | ###### Main
52 |
53 | ```js
54 | const {ipcMain: ipc} = require('electron-better-ipc');
55 |
56 | ipc.answerRenderer('get-emoji', async emojiName => {
57 | const emoji = await getEmoji(emojiName);
58 | return emoji;
59 | });
60 | ```
61 |
62 | ###### Renderer
63 |
64 | ```js
65 | const {ipcRenderer: ipc} = require('electron-better-ipc');
66 |
67 | (async () => {
68 | const emoji = await ipc.callMain('get-emoji', 'unicorn');
69 | console.log(emoji);
70 | //=> '🦄'
71 | })();
72 | ```
73 |
74 | Here we do the inverse of the above, we get an emoji by name in the main process from the renderer process:
75 |
76 | ###### Renderer
77 |
78 | ```js
79 | const {ipcRenderer: ipc} = require('electron-better-ipc');
80 |
81 | ipc.answerMain('get-emoji', async emojiName => {
82 | const emoji = await getEmoji(emojiName);
83 | return emoji;
84 | });
85 | ```
86 |
87 | ###### Main
88 |
89 | ```js
90 | const {ipcMain: ipc} = require('electron-better-ipc');
91 |
92 | (async () => {
93 | const emoji = await ipc.callFocusedRenderer('get-emoji', 'unicorn');
94 | console.log(emoji);
95 | //=> '🦄'
96 | })();
97 | ```
98 |
99 | ## API
100 |
101 | The module exports `ipcMain` and `ipcRenderer` objects which enhance the built-in `ipc` module with some added methods, so you can use them as a replacement for `electron.ipcMain`/`electron.ipcRenderer`.
102 |
103 | ## Main process
104 |
105 | ### ipcMain.callRenderer(browserWindow, channel, data?)
106 |
107 | Send a message to the given window.
108 |
109 | In the renderer process, use `ipcRenderer.answerMain` to reply to this message.
110 |
111 | Returns a `Promise` with the reply from the renderer process.
112 |
113 | #### browserWindow
114 |
115 | Type: `BrowserWindow`
116 |
117 | The window to send the message to.
118 |
119 | #### channel
120 |
121 | Type: `string`
122 |
123 | The channel to send the message on.
124 |
125 | #### data
126 |
127 | Type: `unknown`
128 |
129 | The data to send to the receiver.
130 |
131 | ### ipcMain.callFocusedRenderer(channel, data?)
132 |
133 | Send a message to the focused window, as determined by `electron.BrowserWindow.getFocusedWindow`.
134 |
135 | In the renderer process, use `ipcRenderer.answerMain` to reply to this message.
136 |
137 | Returns a `Promise` with the reply from the renderer process.
138 |
139 | #### channel
140 |
141 | Type: `string`
142 |
143 | The channel to send the message on.
144 |
145 | #### data
146 |
147 | Type: `unknown`
148 |
149 | The data to send to the receiver.
150 |
151 | ### ipcMain.answerRenderer(channel, callback)
152 |
153 | This method listens for a message from `ipcRenderer.callMain` defined in a renderer process and replies back.
154 |
155 | Returns a function, that when called, removes the listener.
156 |
157 | #### channel
158 |
159 | Type: `string`
160 |
161 | The channel to send the message on.
162 |
163 | #### callback(data?, browserWindow)
164 |
165 | Type: `Function | AsyncFunction`
166 |
167 | The return value is sent back to the `ipcRenderer.callMain` in the renderer process.
168 |
169 | ### ipcMain.answerRenderer(browserWindow, channel, callback)
170 |
171 | This method listens for a message from `ipcRenderer.callMain` defined in the given BrowserWindow's renderer process and replies back.
172 |
173 | Returns a function, that when called, removes the listener.
174 |
175 | #### browserWindow
176 |
177 | Type: `BrowserWindow`
178 |
179 | The window for which to expect the message.
180 |
181 | #### channel
182 |
183 | Type: `string`
184 |
185 | The channel to send the message on.
186 |
187 | #### callback(data?, browserWindow)
188 |
189 | Type: `Function | AsyncFunction`
190 |
191 | The return value is sent back to the `ipcRenderer.callMain` in the renderer process.
192 |
193 | ### ipcMain.sendToRenderers(channel, data?)
194 |
195 | Send a message to all renderer processes (windows).
196 |
197 | #### channel
198 |
199 | Type: `string`
200 |
201 | The channel to send the message on.
202 |
203 | #### data
204 |
205 | Type: `unknown`
206 |
207 | The data to send to the receiver.
208 |
209 | ## Renderer process
210 |
211 | ### ipcRenderer.callMain(channel, data?)
212 |
213 | Send a message to the main process.
214 |
215 | In the main process, use `ipcMain.answerRenderer` to reply to this message.
216 |
217 | Returns a `Promise` with the reply from the main process.
218 |
219 | #### channel
220 |
221 | Type: `string`
222 |
223 | The channel to send the message on.
224 |
225 | #### data
226 |
227 | Type: `unknown`
228 |
229 | The data to send to the receiver.
230 |
231 | ### ipcRenderer.answerMain(channel, callback)
232 |
233 | This method listens for a message from `ipcMain.callRenderer` defined in the main process and replies back.
234 |
235 | Returns a function, that when called, removes the listener.
236 |
237 | #### channel
238 |
239 | Type: `string`
240 |
241 | The channel to send the message on.
242 |
243 | #### callback(data?)
244 |
245 | Type: `Function | AsyncFunction`
246 |
247 | The return value is sent back to the `ipcMain.callRenderer` in the main process.
248 |
249 | ## Related
250 |
251 | - [electron-store](https://github.com/sindresorhus/electron-store) - Simple data persistence for your Electron app
252 | - [electron-timber](https://github.com/sindresorhus/electron-timber) - Pretty logger for Electron apps
253 | - [electron-serve](https://github.com/sindresorhus/electron-serve) - Static file serving for Electron apps
254 | - [electron-debug](https://github.com/sindresorhus/electron-debug) - Adds useful debug features to your Electron app
255 | - [electron-unhandled](https://github.com/sindresorhus/electron-unhandled) - Catch unhandled errors and promise rejections in your Electron app
256 | - [electron-context-menu](https://github.com/sindresorhus/electron-context-menu) - Context menu for your Electron app
257 | - [electron-dl](https://github.com/sindresorhus/electron-dl) - Simplified file downloads for your Electron app
258 |
--------------------------------------------------------------------------------