/** * Renderer-side tRPC IPC link for Electron. * * Replaces electron-trpc's ipcLink with a custom implementation that * works with our custom IPC handler + tRPC v11. */ import { observable } from '@trpc/server/observable'; import type { TRPCLink } from '@trpc/client'; import type { AnyRouter } from '@trpc/server'; interface ElectronTRPC { sendMessage: (msg: unknown) => void; onMessage: (cb: (data: unknown) => void) => (() => void) | void; } interface ElectronAI { onStreamChunk: (cb: (data: { token: string; done: boolean }) => void) => () => void; onAction: (cb: (data: { type: string; taskId?: string; count?: number }) => void) => () => void; } declare global { interface Window { electronTRPC: ElectronTRPC; electronAI: ElectronAI; } } type TRPCResponse = { id: number | null; result?: { type: string; data?: unknown }; error?: unknown; }; let nextId = 0; export function ipcLink(): TRPCLink { return () => ({ op }) => observable((observer) => { const id = ++nextId; const { electronTRPC } = window; if (!electronTRPC) { observer.error( new Error( 'Could not find `electronTRPC` global. ' + 'Check that the preload script has been loaded.', ) as any, // eslint-disable-line @typescript-eslint/no-explicit-any ); return; } const unsubscribe = electronTRPC.onMessage((response: unknown) => { const msg = response as TRPCResponse; if (msg.id !== id) return; if ('error' in msg) { observer.error(msg.error as any); // eslint-disable-line @typescript-eslint/no-explicit-any return; } observer.next({ result: msg.result as { type: 'data'; data: unknown }, }); observer.complete(); }); electronTRPC.sendMessage({ method: 'request', operation: { id, type: op.type, path: op.path, input: op.input, }, }); return () => { if (typeof unsubscribe === 'function') unsubscribe(); }; }); }