import type { IpcMain, IpcMainInvokeEvent } from 'electron' import { DomainError } from '../../domain/errors/DomainError' import type { IpcChannel } from '../../shared/contracts/IpcChannels' /** What a channel does with the arguments it was invoked with. */ export type IpcHandler = (...args: readonly unknown[]) => Promise | TResult /** * The one place a channel is registered. * * Errors are normalised on the way out: a domain error crosses as `CODE: message` * so the log drawer shows something a person can act on, and an unexpected one is * logged here rather than vanishing into a rejected promise the window cannot read. */ export class IpcRouter { public constructor (private readonly ipc: IpcMain) {} public handle(channel: IpcChannel, handler: IpcHandler): void { this.ipc.handle(channel, async (_event: IpcMainInvokeEvent, ...args: readonly unknown[]): Promise => { try { return await handler(...args) } catch (error: unknown) { throw this.describe(channel, error) } }) } private describe (channel: IpcChannel, error: unknown): Error { if (error instanceof DomainError) return new Error(`${error.code}: ${error.message}`) if (error instanceof Error) { console.error(`[ipc] ${channel} failed: ${error.message}`) return error } console.error(`[ipc] ${channel} failed: ${String(error)}`) return new Error(String(error)) } }