Cannot remove EventListener from IpcRenderer channel
See original GitHub issueLove this template! Not only did it make me feel more comfortable writing a secure Electron app, but it also helped solidify some fundamental Electron concepts.
I have run into a problem that I can’t seem to nail down. Using this template, I cannot remove an event listener from an IpcRenderer
channel by using .off
or .removeListener
. As long as the listener functions are in scope, they continue to listen and receive messages.
With all the debugging I have done, I am either missing something fundamental or I am running into a cloning/copying issue and my function name is getting lost. Disclaimer, I am running an Angular application under Electron which may contribute to weirdness.
Angular Service
import { Injectable } from '@angular/core';
import { Observable, Subject, of, merge } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class InputFileService {
private _channelName = 'input-file';
constructor() { }
getInputFileData(): Observable<string> {
if (window.electron) {
const responseSubjet = new Subject<string>();
function onReceive(...args: any[]): void {
responseSubjet.next(args[0]);
responseSubjet.complete();
window.electron.stopReceiving('input-file', onReceive);
}
window.electron.receive(this._channelName, onReceive);
return merge(
responseSubjet.asObservable(),
of(window.electron.send(this._channelName))
).pipe(
filter(value => !!value), // double negative to force check truthiness
map(value => value as string)
);
}
return of();
}
}
preload.js
const {
contextBridge,
ipcRenderer
} = require('electron');
const ALLOWED_CHANNELS = [
'input-file',
'context-menu'
];
contextBridge.exposeInMainWorld('electron', {
send: (channel, ...args) => {
// whitelisted channels only
if (ALLOWED_CHANNELS.includes(channel)) {
ipcRenderer.send(channel, ...args);
}
},
receive: (channel, callback) => {
// whitelisted channels only
if (ALLOWED_CHANNELS.includes(channel)) {
ipcRenderer.on(channel, (_, ...args) => {
// deliberately protect `event` as it includes `sender`
callback(...args);
});
}
},
stopReceiving: (channel, listener) => {
// whitelisted channels only
if (ALLOWED_CHANNELS.includes(channel)) {
ipcRenderer.off(channel, listener);
}
},
once: (channel, callback) => {
// whitelisted channels only
if (ALLOWED_CHANNELS.includes(channel)) {
ipcRenderer.once(channel, (_, ...args) => {
// deliberately protect `event` as it includes `sender`
callback(...args);
});
}
},
sendSync: (channel, ...args) => {
// whitelisted channels only
if (ALLOWED_CHANNELS.includes(channel)) {
return ipcRenderer.sendSync(channel, ...args);
}
},
});
Window extension
declare global {
interface Window {
electron: {
send: (channel: string, ...args: any[]) => void,
receive: (channel: string, callback: (...args: any[]) => void) => void,
stopReceiving: (channel: string, listener: (...args: any[]) => void) => void,
once: (channel: string, listener: (...args: any[]) => void) => void,
sendSync: (channel: string, ...args: any[]) => any;
};
}
}
While running under the Visual Studio Code debugger, if I set a breakpoint on the .off
line in preload.js
, the name
property of the listener
function is empty string. It seems like my named function onReceive
is getting passed as an anonymous function somewhere. If I just use once
instead, the listener is removed as expected.
Issue Analytics
- State:
- Created 3 years ago
- Comments:13 (2 by maintainers)
Top GitHub Comments
For someone in the future (here is my solution):
In classic fashion, after many hours of debugging, I discovered the problem and it was so, so obvious. I am suspending my programmer’s license for the weekend.
In
preload
when I callipcRenderer.on
(inside ofreceive
), I am passing in an anonymous function that callscallback
! But, I was trying to removecallback
as a listener – it was never a listener! If I actuallyremoveListener
the anonymous function passed into.on
, wouldn’t you know it, the listener is removed.Side note, I actually followed a different repo with a very similar name, so I was actually barking up the wrong tree coming here. My apologies! Either way, maybe some hopeless soul such as myself will find this in the future and realize they are making same mistake.