@kjn/electron-typesafe-ipc
v2.0.0
Published
TypeSafe IPC functions of Electron with TypeScript.
Downloads
381
Maintainers
Readme
@kjn/electron-typesafe-ipc
This package contains a library that includes type safe versions of the existing electron IPC methods.
Note: Since this is a library, the functionality is opt-in and doesn't change the behaviour in any form. Rather this library focusses on SOLELY adding TypeScript support to the existing electron IPC methods.
Regular Electron IPC (unsafe)
// Type of r is Promise<any>
const r = await ipcRenderer.invoke("substract", 1, 1);
// No warning. Potential cause for run-time errors
const r = await ipcRenderer.invoke("substract", 1, "hello");
With @kjn/electron-typesafe-ipc (safe, better)
The user can explicitly state the types of both the parameters and the return type.
// Type of r is Promise<number>
const r = await invoke<[number, number], number>("substract", 1, 1);
// Displays an error because "hello" isn't a number
const r = await invoke<[number, number], number>("substract", 1, "hello");
With Dedicated IpcChannels (safe, clean, BEST)
Link the handler function to the invoke method by adding structure to the channels. This requires users to use a predefined and specific structure to keep their channels and handlers in.
Dedicated IpcChannel will however improve the Separation of Concern and it complies with the Single Responsibility Principle. Therefore in terms of Architecture its often the right choice.
// [Can be defined anywhere] Should implement the IpcChannel interface
const substractIpcChannel = {
name: "SUBSTRACT_CHANNEL",
handler: (ev: IpcMainInvokeEvent, n1: number, n2: number): number => n1 - n2,
};
// [@main.ts] this will add the handler to the 'SUBSTRACT_CHANNEL` as defined in the object.
registerIpcChannel(substractIpcChannel);
// [@preload.ts] this will call the substractIpcChannel handler with the parameters 10 and 5
// additionally both the return type and the parameters are strongly typed.
const r = invokeIpcChannel(substractIpcChannel, 10, 5); // == Promise<number>
Table of content
Usage
Installation
Install the package with npm or yarn (or just copy the files whatever works)
npm install --save-dev @kjn/electron-typesafe-ipc
yarn add -D @kjn/electron-typesafe-ipc
Import the desired method where relevant
import { invoke, handle, invokeIpcChannel, registerIpcChannel } from "@kjn/electron-typesafe-ipc";
// For more details on the API interface check the docs below
Use the typesafe methods according to your needs.
Recommended Usage
Use the invokeIpcChannel
and registerIpcChannel
methods instead of the original invoke
and handle
calls. This will work the best for large scale projects and forces you to adapt to good architecture practices.
import { IpcChannel } from "@kjn/electron-typesafe-ipc";
const substractIpcChannel: IpcChannel<[number, number], number> = {
name: "SUBSTRACT_CHANNEL",
handler: (ev, n1: number, n2: number) => n1 + n2,
};
// register a ipcChannel handler
registerIpcChannel(substractIpcChannel);
// invoke the handler of the ipcChannel AND benefit from the typehints that it provides
const r = await invokeIpcChannel(substractIpcChannel, 10, 4);
Alternative Usage
The invoke
and handle
calls are cross compatible replacements for the ipcRenderer.invoke
and ipcMain.handle
calls respectively. Therefore they can be swapped in and out without any implications to the logic of the code.
type SubstractParamsType = [number, number];
type SubstractReturnType = number;
// e.g. add a handler, the generics force the handler to accept 2 numbers as input and return a number as output
handle<SubstractParamsType, SubstractReturnType>("substract", (ev, n1, n2) => n1 + n2);
// e.g. invoke a handler AND benefit from the typehints that it provides
const result = invoke<SubstractParamsType, SubstractReturnType>("substract", 10, 30);
The first generic in the handle
and invoke
calls represents the parameters of the handler as an array. The second generic represents the return type.
API
Handle
import { handle } from "@kjn/electron-typesafe-ipc";
The handle function contains 2 generics that can be passed to it: P
and R
to represent the parameters and return type of the handler function respectively.
handle<P, R>(<channelName>, <handlerFunction>)
Handle P Generic
P
(generic handle<P, _>), indicates the array of the parameter types of the handler, a few examples:
// TypeHint to use: 2 numbers as paremeters for the handler
handle<[number, number]>("SUBSTRACT_CHANNEL", (ev, n1, n2) => n1 + n2);
// TypeHint to use: no parameters for the handler
const trueChannelHandler = (ev) => !!ev;
handle<[]>("TRUE_CHANNEL", trueChannelHandler);
// TypeHint to use: a string, number and boolean as parameters for the handler
function randomChannelHandler(ev, str, num, bool) {
if (str === "hello" || num < 1 || bool === true) {
throw Error("Error");
}
}
handle<[string, number, boolean]>("RANDOM_CHANNEL", (ev, str, num, bool) =>
randomChannelHandler(ev, str, num + 2, bool)
);
Note: P doesn't include the first (and always present) IpcMainInvokeEvent
parameter
Handle R Generic
R
(generic handle<_, R>), indicates the return type of the handler function, a few examples:
// Expect a number as the return type of the handler
handle<[], number>("SUBSTRACT_CHANNEL", (ev, n1: number, n2: number) => n1 + n2);
// Expect null as the return type of the handler
handle<[], null>("LOG_CHANNEL", (ev) => {
console.log("log");
return null;
});
// Expect a boolean OR string as the return type of the handler
handle<[num], boolean | string>("IS_PIE_CHANNEL", (ev, n1) => {
if (n1 === 314) {
return "pie?";
}
return n1 > 100;
});
Invoke
import { invoke } from "@kjn/electron-typesafe-ipc";
The invoke function contains 2 generics that can be passed to it: P
and R
, which represent the parameters type and return type of the handler call respectively.
invoke<P, R>(<channelName>, <param1: P[0]>, <param2: P[1]>, ..., <paramX: P[X]>): R
Invoke P Generic
P
(generic invoke<P, _>), indicates the array of the parameter types, a few examples:
// Indicates that there are 2 parameters, both numbers
const r1 = invoke<[number, number]>("SUBSTRACT_CHANNEL", 100, 10); // == Promise<any>
// Indicates that there are no parameters
const r2 = invoke<[]>("TRUE_CHANNEL"); // == Promise<any>
// Indicates that there is a string, number and boolean as parameters
const r3 = invoke<[string, number, boolean]>("RANDOM_CHANNEL", "Hello world", 10, false); // == Promise<any>
Invoke R Generic
R
(generic invoke<_, R>), indicates the return type, a few examples:
// Indicates that the return type is number
const r1 = invoke<[number, number], number>("SUBSTRACT_CHANNEL", 100, 10); // == Promise<number>
// Indicates that the return type is null
const r2 = invoke<[], null>("LOG_CHANNEL"); // Promise<null>
// Indicates that the return type is either boolean OR string
const r3 = invoke<[string], boolean | string>("RANDOM_CHANNEL", "str as input"); // Promise<boolean | string>
Note: since invoke is a asynchronous call, the return types are wrapped in Promises
registerIpcChannel
import { registerIpcChannel } from "@kjn/electron-typesafe-ipc";
Add a handler for an ipc channel, requires an object that implements the IpcChannel interface. Typechecking will be handled automatically.
import { IpcChannel } from "@kjn/electron-typesafe-ipc";
const substractIpcChannel: IpcChannel<[number, number], number> = {
name: "SUBSTRACT_CHANNEL",
handler: (ev, n1: number, n2: number) => n1 - n2,
};
registerIpcChannel(substractIpcChannel);
InvokeIpcChannel
import { invokeIpcChannel } from "@kjn/electron-typesafe-ipc";
Invoke the handler of an ipc channel, requires an object that implements the IpcChannel interface. Typechecking will be handled automatically.
Also relies on the handler existing for that ipcChannel.
import { IpcChannel } from "@kjn/electron-typesafe-ipc";
const substractIpcChannel: IpcChannel<[number, number], number> = {
name: "SUBSTRACT_CHANNEL",
handler: (ev, n1: number, n2: number) => n1 - n2,
};
const result = await invokeIpcChannel(substractIpcChannel, 10, 100);
IpcChannel Interface
The IpcChannel interface defines the simplest blueprint to which an object should adhere to be used with the registerIpcChannel
and invokeIpcChannel
calls.
The IpcChannel interface contains 2 generics that can be passed to it: P
and R
to represent the parameters and return type of the handler respectively.
IpcChannel<P, R>
IpcChannel P Generic
P
(generic IpcChannel<P, _>), indicates the array of the parameter types of the handler, a few examples:
// 2 numbers as parameters for the handler
const substractIpcChannel: IpcChannel<[number, number]> = {
name: "SUBSTRACT_CHANNEL",
handler: (ev, n1, n2) => n1 - n2, // TypeScript will treath n1 and n2 as numbers
};
// no parameters for the handler
const printLogIpcChannel: IpcChannel<[]> = {
name: "PRINT_LOG_CHANNEL",
handler: (ev) => {
console.log("log");
},
};
// a number and boolean as parameters for the handler
const ageCheckIpcChannel: IpcChannel<[number, boolean]> = {
name: "AGE_CHECK_CHANNEL",
handler: (ev, age, adultRequired) => {
if (age < 18 && adultRequired) {
return false;
}
return true;
},
};
IpcChannel R Generic
R
(generic IpcChannel<_, R>), indicates the return type of the handler, a few examples:
// number as a return type for the handler
const getMsIpcChannel: IpcChannel<[], number> = {
name: "GET_MS_CHANNEL",
handler: (ev) => {
return new Date().getMilliseconds();
},
};
// CustomType as return type for the handler
const customTypeIpcChannel: IpcChannel<[], CustomType> = {
name: "CUSTOM_TYPE_CHANNEL",
handler: (ev) => {
return { x: 1, y: 92 } as CustomType;
},
};
type CustomType = {
x: number;
y: number;
};