import type { IpcResult, FileResult, FileEntry, PdfOptions, DocxOptions, HtmlOptions, ExportResult, BatchItem, BatchOptions, BatchResult, } from '@/types/ipc'; type ChannelMissing = { code: 'CHANNEL_MISSING'; message: string }; function wrap(fn: () => Promise): Promise> { if (typeof window === 'undefined' || !window.electronAPI) { return Promise.resolve({ ok: false, error: { code: 'NO_BRIDGE', message: 'window.electronAPI is unavailable' }, }); } return fn().then( (data) => ({ ok: true as const, data }), (err: Error) => ({ ok: false as const, error: { code: err.name || 'IPC_ERROR', message: err.message || String(err) }, }), ); } function safeCall Promise>( channel: string, method: string, ...args: Parameters ): Promise> | ChannelMissing>> { if (typeof window === 'undefined' || !window.electronAPI) { return Promise.resolve({ ok: false, error: { code: 'NO_BRIDGE', message: 'window.electronAPI is unavailable' }, }); } const target = (window.electronAPI as any)[channel]?.[method]; if (!target) { return Promise.resolve({ ok: false, error: { code: 'CHANNEL_MISSING', message: `Missing channel: ${channel}.${method}` }, }); } if (typeof target !== 'function') { return Promise.resolve({ ok: false, error: { code: 'CHANNEL_MISSING', message: `Not a function: ${channel}.${method}` }, }); } return wrap(() => target(...args)); } export const ipc = { file: { open: (): Promise> => safeCall('file', 'open'), read: (path: string): Promise> => safeCall('file', 'read', path), write: (path: string, content: string): Promise> => safeCall('file', 'write', path, content), list: (dir: string): Promise> => safeCall('file', 'list', dir), pickFolder: (): Promise> => safeCall('file', 'pickFolder'), pickFile: (): Promise> => safeCall('file', 'pickFile'), onChange: (cb: (path: string) => void): (() => void) => { if (typeof window === 'undefined' || !window.electronAPI?.file?.onChange) { return () => {}; } return window.electronAPI.file.onChange(cb); }, search: (args: { rootPath: string; query: string; isRegex: boolean; caseSensitive: boolean }): Promise | ChannelMissing>> => safeCall('file', 'search', args), gitStatus: (args: { rootPath: string }): Promise | ChannelMissing>> => safeCall('file', 'gitStatus', args), writeBuffer: (args: { path: string; buffer: Uint8Array }): Promise> => safeCall('file', 'writeBuffer', args), }, print: { show: (args: { html: string }): Promise> => safeCall('print', 'show', args), doPrint: (args: { withStyles?: boolean }): Promise> => safeCall('print', 'doPrint', args), }, export: { pdf: (opts: PdfOptions): Promise> => safeCall('export', 'pdf', opts), docx: (opts: DocxOptions): Promise> => safeCall('export', 'docx', opts), html: (opts: HtmlOptions): Promise> => safeCall('export', 'html', opts), batch: (items: BatchItem[], opts: BatchOptions): Promise> => safeCall('export', 'batch', items, opts), }, app: { getVersion: (): Promise> => safeCall('app', 'getVersion'), openExternal: (url: string): Promise> => safeCall('app', 'openExternal', url), showItemInFolder: (path: string): Promise> => safeCall('app', 'showItemInFolder', path), showSaveDialog: (args?: { title?: string; defaultPath?: string }): Promise> => safeCall('app', 'showSaveDialog', args), }, menu: { /** * Subscribe to a native-menu IPC channel. The callback receives the * raw payload(s) from the main process. Returns an unsubscribe fn. */ on: (channel: string, callback: (...args: unknown[]) => void): (() => void) => { if (typeof window === 'undefined' || !window.electronAPI?.on) { return () => {}; } return window.electronAPI.on(channel, callback as (...a: any[]) => void); }, }, updater: { check: (): Promise> => safeCall('updater', 'check'), install: (): Promise> => safeCall('updater', 'install'), getState: (): Promise> => safeCall('updater', 'getState'), onStatus: (cb: (payload: unknown) => void): (() => void) => { if (typeof window === 'undefined' || !window.electronAPI?.updater?.onStatus) { return () => {}; } return window.electronAPI.updater.onStatus(cb); }, }, crash: { read: (): Promise> => safeCall('crash', 'read'), openDir: (): Promise> => safeCall('crash', 'openDir'), delete: (filename: string): Promise> => safeCall('crash', 'delete', filename), }, };