Files
bs-manager/src/main/services/ipc.service.ts
T

81 lines
3.2 KiB
TypeScript

import { IpcRequest } from "shared/models/ipc";
import { BrowserWindow, IpcMainEvent, WebContents, ipcMain } from "electron";
import { Observable } from "rxjs";
import { IpcCompleteChannel, IpcErrorChannel, IpcTearDownChannel } from "shared/models/ipc/ipc-response.interface";
import { IpcReplier } from "shared/models/ipc/ipc-request.interface";
import { serializeError } from 'serialize-error';
import log from "electron-log";
import { IpcChannels, IpcRequestType, IpcResponseType } from "shared/models/ipc/ipc-routes";
export class IpcService {
private static instance: IpcService;
public static getInstance(): IpcService {
if (!IpcService.instance) {
IpcService.instance = new IpcService();
}
return IpcService.instance;
}
private constructor() {}
private getErrorChannel(channel: string): IpcErrorChannel {
return `${channel}_error`;
}
private getCompleteChannel(channel: string): IpcCompleteChannel {
return `${channel}_complete`;
}
private getTearDownChannel(channel: string): IpcTearDownChannel {
return `${channel}_teardown`;
}
private buildProxyListener<C extends IpcChannels>(listener: IpcListenerFromChannel<C>) {
return (event: IpcMainEvent, req: IpcRequest<IpcRequestType<C>>) => {
const window = BrowserWindow.fromWebContents(event.sender);
const replier = (data: Observable<unknown>) => this.connectStream(req.responceChannel, window, data);
listener(req.args, replier, event.sender);
};
}
public send<T>(channel: string, window: BrowserWindow, response?: T | Error): void {
if(window.webContents?.isDestroyed()){ return; }
window.webContents?.send(channel, response);
}
private connectStream(channel: string, window: BrowserWindow, observable: Observable<unknown>): void {
const sub = observable.subscribe({
next: data => this.send(channel, window, data),
error: error => {
log.error(error, error?.code, error?.data);
this.send(this.getErrorChannel(channel), window, serializeError(error));
},
complete: () => this.send(this.getCompleteChannel(channel), window)
})
const unsubscribe = () => sub.unsubscribe();
window.webContents.once("destroyed", unsubscribe);
window.webContents.ipc.once(this.getTearDownChannel(channel), unsubscribe);
sub.add(() => {
window.webContents.ipc.removeAllListeners(this.getTearDownChannel(channel));
window.webContents.removeListener("destroyed", unsubscribe);
});
}
public on<C extends IpcChannels>(channel: C, listener: IpcListenerFromChannel<C>, ipc = ipcMain): void {
ipc.on(channel as string, this.buildProxyListener(listener));
}
public once<C extends IpcChannels>(channel: C, listener: IpcListenerFromChannel<C>, ipc = ipcMain): void {
ipc.once(channel as string, this.buildProxyListener(listener));
}
}
type IpcListener<TRequest = unknown, TResponse = unknown> = (req: TRequest, replier: IpcReplier<TResponse>, webContents: WebContents) => void | Promise<void>;
type IpcListenerFromChannel<C extends IpcChannels> = IpcListener<IpcRequestType<C>, IpcResponseType<C>>;