npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@electron-tools/ipc

v1.0.2

Published

electron ipc toolkit

Downloads

4

Readme

@electron-tools/ipc

Introduction

Inspired by VSCode IPC implement. A more simple and more powerful way to use Electron IPC. Support service Invoke/Acknowledgement mode and event Subscribe/Publish mode between main process and renderer process.

Installation

# npm
npm install @electron-tools/ipc
# yarn
yarn add @electron-tools/ipc
# pnpm
pnpm add @electron-tools/ipc

Features

Invoke service with acknowledgement between main process and renderer process

Before using @electron-tools/ipc,we have to use event.sender.send to response to renderer and use try catch to notice client current invoke operation has error, it is not clear.

// main process
import { ipcMain } from 'electron';

ipcMain.on('getUserById', async (event, id) => {
  try {
    const userInfo = await userService.getUserById(id);
    event.sender.send('getUserById:response', userInfo);
  } catch (e) {
    event.sender.send('getUserById:error', e.message);
  }
});
// renderer process
import { ipcRenderer } from 'electron';

function getUserById(id) {
  return new Promise((resolve, reject) => {
    ipcRenderer.on('getUserById:response', (event, userInfo) => {
      resolve(userInfo);
    });
    ipcRenderer.on('getUserById:error', (event, errorMessage) => {
      resolve(new Error(errorMessage));
    });
    ipcRenderer.send('getUserById', id);
  });
}

After using @electron-tools/ipc

// main process
import { ElectronIPCMain } from '@electron-tools/ipc';

const electronIPCMain = new ElectronIPCMain();
// register user ServerChannel to handle client request.
electronIPCMain.registerChannel('user', {
  invoke(ctx, command, ...args) {
    switch (command) {
      case 'getUserById':
        // the only thing you need to do is invoke the service method.
        // if userService#getUserById throw a error,
        // error message will auto response to client.
        return userService.getUserById(args[0]);
    }
  },
});
// renderer process
import { ElectronIPCRenderer } from '@electron-tools/ipc';

const ipcRenderer = new ElectronIPCRenderer('main_window');
function getUserById(id) {
  // get user channel and invoke with command getUserById.
  // invoke returns type will be Promise<UserInfo>.
  // if server throw a error in execution,
  // invoke will reject with a error.
  return ipcRenderer.getChannel('user').invoke('getUserById', id);
}

Since Electron@7, we can use ipcMain.handle with ipcRenderer.invoketo do something like above show. Actually, @electron-tools/ipc use ipcRender.send and ipcMain.on under the hood. in this example, we make a invoke request from main process to renderer process, but we can use the same way to make a invoke request from renderer process to main process, let's do it.

// main process
import { ElectronIPCMain } from '@electron-tools/ipc';

const electronIPCMain = new ElectronIPCMain();

async function getUserById(id) {
  // we should set the target client by the first argument of getChannel which mean the client ctx.
  // because the renderer process with ctx 'main_window' may be more than one,
  // invoke returns with type Promise<Array<UserInfo>>
  const [user] = await electronIPCMain.getChannel('main_window', 'user').invoke('getUserById', id);
  return user;
}
// renderer process
import { ElectronIPCRenderer } from '@electron-tools/ipc';

const ipcRenderer = new ElectronIPCRenderer('main_window');
ipcRenderer.registerChannel('user', {
  invoke(ctx, command, ...args) {
    switch (command) {
      case 'getUserById':
        return userService.getUserById(args[0]);
    }
  },
});

Sometimes a server execution will take a long time, we may want to cancel the execution because it is outdated. Channel#invoke method returns type will have a cancel properties. which can cannel the invoke manually. for example:

// renderer process
import { ElectronIPCRenderer } from '@electron-tools/ipc';

const ipcRenderer = new ElectronIPCRenderer('main_window');
function getUserById(id) {
  return ipcRenderer.getChannel('user').invoke('getUserById', id);
}

const userPromise = getUserById('xxx');
userPromise.cancel();

Subscribe event which will be published later

Publish/Subscribe design pattern is widely used in daily development. @electron-tools/ipc also support it. Renderer process can subscribe main process event which will be published later and vice versa.

// main process
import { ElectronIPCMain } from '@electron-tools/ipc';
import { EventEmitter } from 'events';

const eventBus = new EventEmitter();

setInterval(() => {
  eventBus.emit('userStatusChange', 'user login');
}, 1000);

const electronIPCMain = new ElectronIPCMain();
electronIPCMain.registerChannel('user', {
  event(ctx, event) {
    switch (event) {
      case 'userStatusChange':
        return {
          subscribe(cb) {
            eventBus.on('userStatusChange', cb);
            return () => eventBus.off('userStatusChange', cb);
          },
        };
    }
  },
});
// renderer process
import { ElectronIPCRenderer } from '@electron-tools/ipc';

const ipcClient = new ElectronIPCRenderer('main_window');
// subscription has a unscribe method to stop listen userStatusChange event.
const subscription = ipcClient
  .getChannel('user')
  .event('userStatusChange')
  .subscribe((latestStatus) => {
    console.log(latestStatus);
  });
setTimeout(() => {
  // after subscribe, main process will auto remove userStatusChange listener.
  // and will never send and ipc message except userStatusChange event been subscripted again.
  subscription.unsubscribe();
}, 5000);

// user login
// user login
// user login
// user login

@electron-tools/ipc use rxjs subscribe/publish pattern under the hood. so it really simple when working with rxjs.

// main process
import { ElectronIPCMain } from '@electron-tools/ipc';
import { Subject } from 'rxjs';

const userStatusSubject = new Subject();

setInterval(() => {
  userStatusSubject.next('user login');
}, 1000);

const electronIPCMain = new ElectronIPCMain();
electronIPCMain.registerChannel('user', {
  event(ctx, event) {
    switch (event) {
      case 'userStatusChange':
        return userStatusSubject;
    }
  },
});

Concept

Connection

Represent connection between main process(ElectronIPCMain) and renderer process(ElectronIPCRenderer). ElectronIPCMain maintains a group of connection. ElectronIPCRenderer maintains only one connection with ElectronIPCMain.

ServerChannel

ServerChannel has two method, invoke and event, connect with ClientChannel by connection. Represent a group of service and subscribable, service can be invoked and subscribable can be subscribe through ClientChannel.

ClientChannel

ClientChannel is a proxy channel of ServiceChannel, also contains invoke and event method.

API Reference

interface Subscription {
  unsubscribe(): void;
}

interface Subscribable<TData = any> {
  subscribe(receiver: (data: TData) => void): Subscription | void;
}

interface ClientSubscribable<TData = any> {
  subscribe(receiver: (data: TData) => void): Subscription;
}

interface ServerChannel<TContext = string> {
  invoke?(ctx: TContext, command: string, ...args: any[]): any;
  event?(ctx: TContext, event: string): Subscribable<any>;
}

interface Cancellable {
  cancel(reason?: string): void;
}

type CancellablePromise<T = any> = Promise<T> & Cancellable;

interface ClientChannel {
  invoke<TArgs extends any[] = any[], TReturn = any>(command: string, ...args: TArgs): CancellablePromise<TReturn>;
  event<TData = any>(event: string): ClientSubscribable<TData>;
}

interface ElectronIPCMain<TContext = string> {
  registerChannel(channel: string, serverChannel: ServerChannel): void;
  getChannel(ctx: TContext, channel: string): ClientChannel;
  // destroy will disconect all connections.
  destroy(): void;
}

interface ElectronIPCRenderer {
  registerChannel(channel: string, serverChannel: ServerChannel): void;
  getChannel(ctx: TContext, channel: string): ClientChannel;
  // destroy will disconect current connection.
  destroy(): void;
}

Something you should keep in mind

  1. ElectronIPCMain and ElectronIPCRenderer should be initialized only once, or says it should be a singleton instance. You can initialize in one place and exports the instance, all other source want to use should import the instance.