question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Cannot remove EventListener from IpcRenderer channel

See original GitHub issue

Love 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:closed
  • Created 3 years ago
  • Comments:13 (2 by maintainers)

github_iconTop GitHub Comments

43reactions
Vishal1419commented, Feb 3, 2021

For someone in the future (here is my solution):

const { contextBridge, ipcRenderer } = require('electron')
const { CONTENT_EVENTS } = require('../../ipc-events');

contextBridge.exposeInMainWorld(
  'electron', {
    send: (channel, data) => {
      if (Object.values(CONTENT_EVENTS.C2E).includes(channel)) {
        ipcRenderer.send(channel, data)
      }
    },
    receive: (channel, func) => {
      if (Object.values(CONTENT_EVENTS.E2C).includes(channel)) {
        // Deliberately strip event as it includes `sender` 
        const subscription = (event, ...args) => func(...args);
        ipcRenderer.on(channel, subscription);
        return () => {
          ipcRenderer.removeListener(channel, subscription);
        }
      }
    },
    receiveOnce: (channel, func) => {
      if (Object.values(CONTENT_EVENTS.E2C).includes(channel)) {
        // Deliberately strip event as it includes `sender` 
        ipcRenderer.once(channel, (event, ...args) => func(...args))
      }
    },
    removeAllListeners: (channel, func) => {
      if (Object.values(CONTENT_EVENTS.E2C).includes(channel)) {
        ipcRenderer.removeAllListeners(channel)
      }
    }
  }
)

6reactions
nalexander50commented, Dec 12, 2020

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 call ipcRenderer.on (inside of receive), I am passing in an anonymous function that calls callback! But, I was trying to remove callback as a listener – it was never a listener! If I actually removeListener 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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to unregister from ipcRenderer.on event listener?
The solution is straightforward, remove the event listener after it receives an event. I have tried doing something like: this.electronService.
Read more >
ipcRenderer - Electron
Adds a one time listener function for the event. This listener is invoked only the next time a message is sent to channel...
Read more >
electron.IpcRenderer.removeListener JavaScript and Node.js ...
How to use. removeListener. function. in. IpcRenderer. Best JavaScript code snippets using electron.IpcRenderer.removeListener(Showing ...
Read more >
ipcrenderer invoke - You.com | The Search Engine You Control
How to pass ipcRenderer.invoke handler answer to electron renderer process using ... invoke: (channel: string, data: any) => { // whitelist channels let ......
Read more >
electron ipcRenderer JavaScript Examples - ProgramCreek.com
This page shows JavaScript code examples of electron ipcRenderer. ... setMaxListeners(200000) ipcRenderer.on(channel, async (event, ...args) => { try ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found