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(listener: IpcListenerFromChannel) { return (event: IpcMainEvent, req: IpcRequest>) => { const window = BrowserWindow.fromWebContents(event.sender); const replier = (data: Observable) => this.connectStream(req.responceChannel, window, data); listener(req.args, replier, event.sender); }; } public send(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): 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(channel: C, listener: IpcListenerFromChannel, ipc = ipcMain): void { ipc.on(channel as string, this.buildProxyListener(listener)); } public once(channel: C, listener: IpcListenerFromChannel, ipc = ipcMain): void { ipc.once(channel as string, this.buildProxyListener(listener)); } } type IpcListener = (req: TRequest, replier: IpcReplier, webContents: WebContents) => void | Promise; type IpcListenerFromChannel = IpcListener, IpcResponseType>;