electron-ipc-logger
v1.1.0
Published
Log and display all user-defined IPC traffic in an electron app.
Downloads
86
Readme
electron-ipc-logger
Log and display all user-defined IPC traffic in an electron app.
Configuring it
npm install --save-dev electron-ipc-logger
Then, in your main process code just add the following once your app is ready:
import { installIpcLogger } from 'electron-ipc-logger';
app.whenReady().then(async () => {
// ...
await installIpcLogger();
// ...
});
Options
Complete list of options accepted by installIpcLogger
:
| Option | Type | Default | Notes |
| ------------------- | ------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| closeIfLastWindow
| boolean
| true
| When another window is closed, if the IPC Logger is opened and it's the only remaining window, it will be automatically closed as well. This is usually the wanted behavior, but can be set to false
to check data before the application exits. |
| openUiOnStart
| boolean
| true
| When true
the UI window will show as soon as it's ready. If set to false
, it can be opened at the convenient time by calling openIpcLoggerWindow
|
| parent
| BrowserWindow
| undefined
| When provided, the IPC Logger UI Window will be closed when the parent is closed even if other windows are still open. It also will make the IPC Logger UI Window to be displayed on top of the parent
at every moment. |
| disable
| boolean
| false
| Allows to quickly enable or disable logging without overriding other options. |
| mainToRenderer
| boolean
| true
| Log messages sent from the main process to the renderer process. |
| rendererToMain
| boolean
| true
| Log messages sent from the renderer process to the main process. |
| consoleOutput
| boolean
| false
| Output the intercepted messages to the console (from the main process). |
| logSystemMessages
| boolean
| false
| true
to include system messages in the log, false
to exclude them. (Messages prefixed with ELECTRON
or CHROME
) |
| shortcut
| string
| boolean
| 'CmdOrCtrl+Shift+D'
| Accelerator (key shortcut) to register globally to open the IPC Logger UI window. Can be set to false
or empty string ''
to disable it (true
will just keep the default shortcut) |
| filter
| (data: IpcLogData) => boolean
| undefined
| Allows specifying what IPC messages should be logged or not. Note that unless logSystemMessages
is set to true
, the filter
won't receive data from IPC channels considered as system messages. |
| onIpcMessage
| (channel: string, ...data: any[]) => void
| undefined
| Callback to handle the intercepted messages with custom code (i.e. log it to a file, etc.) |
F.A.Q
The IPC Logger UI shows even on production build!
Yes, you are not supposed to install it when building it for production. There are two ways to disable it:
① Manage it manually:
if (!IS_PRODUCTION) {
await installIpcLogger();
}
② Using the disable
option for a cleaner code
await installIpcLogger({ disable: IS_PRODUCTION });
The IPC Logger UI Window stays open when the app closes
Providing a parent
(i.e. your app main window) enables automatically closing the IPC Logger UI window when your app closes.
import { installIpcLogger } from 'electron-ipc-logger';
app.whenReady().then(async () => {
// ...
await installIpcLogger({ parent: mainWindow });
// ...
});
If your main window is created later (for any reason) and it's not available when installing this module, worry not! You can always provide the parent once your window is available:
import {
installIpcLogger,
setIpcLoggerParentWindow,
} from 'electron-ipc-logger';
app.whenReady().then(async () => {
// no parent available yet...
await installIpcLogger();
});
// once it's ready
const mainWindow = createMainWindow();
setIpcLoggerParentWindow(mainWindow);
The IPC Logger UI Window is always shown at the start
Yes, that's how it's set to work by default as the premise is that in development mode (every time the extension is installed), it would be desired to debug IPC messages.
You can however, set it to be hidden by default and open/close it on a better timing (i.e. with some custom button or app menu):
import { installIpcLogger, openIpcLoggerWindow } from 'electron-ipc-logger';
app.whenReady().then(async () => {
// no parent available yet...
await installIpcLogger({ openUiOnStart: false });
});
// To open the UI, you can call this function from an IPC listener or a menu in your app (note that IPC channels need to be handled manually):
ipcMain.handle('YOUR_OPEN_UI_LOGGER_IPC_CHANNEL', () => openIpcLoggerWindow());
See "I closed the IPC Logger UI window. How to open it again?" for more details.
I closed the IPC Logger UI window. How to open it again?
There are at least three ways to do it. From the easiest one:
① Accelerators
The easiest one is to rely on accelerators (see the shortcut
option when calling installIpcLogger()
).
import { installIpcLogger } from 'electron-ipc-logger';
app.whenReady().then(async () => {
await installIpcLogger({
// there's no need to add this option as it's the default one,
// but this shows how to change the default shortcut to a custom one
shortcut: 'CommandOrControl+Shift+L',
});
});
By default the IPC Logger UI window can be opened anytime using Control + Shift + D
(⌘ + Shift + D
on Mac). The shortcut can be customized or disabled.
② Menu
Set up a Menu with an item to open the IPC Logger, that will call openIpcLoggerWindow()
from the main process.
import { app, Menu } from 'electron';
import { openIpcLoggerWindow } from 'electron-ipc-logger';
function createDebugMenu(): void {
// other options might be desired in the template :)
const template = [
{
label: 'Debug',
submenu: [
{
label: 'Open IPC Logger',
click: openIpcLoggerWindow,
},
],
},
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
app.whenReady().then(async () => {
createDebugMenu();
// Note that if the windows are created with `autoHideMenuBar: true,` ALT
// needs to be pressed for the menu to be shown
});
③ IPC Messaging
Set up a button, command or custom trigger on your application that sends an IPC message on a custom channel of your choice and then gets processed by the main process to call the said openIpcLoggerWindow()
function.
// [on your main process]
import { app } from 'electron';
import { openIpcLoggerWindow } from 'electron-ipc-logger';
ipcMain.handle('openIpcLogger', () => {
openIpcLoggerWindow();
});
// [on your BrowserWindow]
// asumming you have `<button id="open-ipc-logger">Open IPC Logger</button>` on your HTML
document.getElementById('open-ipc-logger').addEventListener('click', () => {
// note that ipcRenderer needs to be exposed or the method enabled on your preload script
window.api.ipcRenderer.invoke('openIpcLogger');
});
How to permantently filter IPC communication?
a.k.a. I'm using a 3rd party module that uses IPC messages internally, and I don't care about them!
While it's always possible to filter and search messages in the UI Window, doing that every time might be tiring (specially if there's a lot of noise)
Using the filter
option allows ignoring messages to drop from the log:
import { installIpcLogger } from 'electron-ipc-logger';
app.whenReady().then(async () => {
// i.e. let's say we want to ignore messages from i18n-next loading resources,
// which use the `Readfile-Request` and `Readfile-Response` channels:
await installIpcLogger({
filter: ({ channel }) =>
![`Readfile-Request`, `Readfile-Response`].includes(channel),
});
});
Why is this not integrated on dev-tools?
That was actually the initial approach for this module: Providing an extra IPC
panel in the dev-tools window (the same way React Developer Tools works)... but the communication between the main process / dev tools window / IPC... was getting too complicated, so I opted to provide a UI Window that works for now, and maybe continue the dev-tools approach at some point in the future.
How does internally works?
a.k.a. helping myself remembering the underlying architecture (or explaining the insides to collaborators).
First thing required is to call installIpcLogger()
from the main process.
This will create a UI browser window for the IpcLogger to display the data. Yes, this is always done whether the UI window is shown or not. This is a package for debugging IPC messages so, don't forget to disable it on production (by not calling installIpcLogger
or by passing the disabled: true
option).
Once a reference to the window is available, all the relevant IPC methods in ipcMain
will be hijacked, allowing the capture of incoming and outcoming messages, but this alone will not be able capture messages sent when replying incoming events (as it's not done through the ipcMain
object, but using the WebContents
of the sender directly).
For that, BrowserWindow.WebContents
need to be hijacked as well, which is done by getting all the existing windows and listening for the creation of new ones (with the help of the browser-window-created event).
The UI window is initialized with the preload script which exposes some data and the IpcRenderer
, which will be used to send/receive the captured IPC messages.
IPC messages are captured always in the main process and sent to the UI window using IPCs as well. The installed preload script will store them and make them available for the UI.
To optimize sending messages with log updates to the UI Window, it keeps track of what was the last message sent, and only sends newer ones. However, internally every log is preserved on memory so method called with .invoke
can be updated. Updated methods are always sent regardless if the original message (without the returned value) was already sent or not.
The UI window loads an empty page that executes index.tsx rendering the UI itself via React. This UI registers a listener to react when new data is received from the main process, and from here is just displaying the data as any usual web-app.