/ ipc-adapter.ts
ipc-adapter.ts
1 // Communication adapter that works in both Electron and Web modes 2 import { io, Socket } from "socket.io-client"; 3 4 type EventCallback = (event: any, ...args: any[]) => void; 5 6 export interface IPCAdapter { 7 send(channel: string, ...args: any[]): void; 8 invoke(channel: string, ...args: any[]): Promise<any>; 9 on(channel: string, callback: EventCallback): void; 10 } 11 12 class ElectronAdapter implements IPCAdapter { 13 private ipcRenderer: any; 14 15 constructor() { 16 this.ipcRenderer = require("electron").ipcRenderer; 17 } 18 19 send(channel: string, ...args: any[]): void { 20 this.ipcRenderer.send(channel, ...args); 21 } 22 23 invoke(channel: string, ...args: any[]): Promise<any> { 24 return this.ipcRenderer.invoke(channel, ...args); 25 } 26 27 on(channel: string, callback: EventCallback): void { 28 this.ipcRenderer.on(channel, callback); 29 } 30 } 31 32 class WebAdapter implements IPCAdapter { 33 private socket: Socket; 34 private responseHandlers = new Map<number, { resolve: (value: any) => void; reject: (reason?: any) => void }>(); 35 private requestCounter = 0; 36 37 constructor() { 38 this.socket = io(); 39 40 // Handle invoke responses from server 41 this.socket.on("invoke-response", (requestId: number, error: any, result: any) => { 42 const handler = this.responseHandlers.get(requestId); 43 if (handler) { 44 this.responseHandlers.delete(requestId); 45 if (error) { 46 handler.reject(error); 47 } else { 48 handler.resolve(result); 49 } 50 } 51 }); 52 } 53 54 send(channel: string, ...args: any[]): void { 55 this.socket.emit(channel, ...args); 56 } 57 58 invoke(channel: string, ...args: any[]): Promise<any> { 59 return new Promise((resolve, reject) => { 60 const requestId = this.requestCounter++; 61 this.responseHandlers.set(requestId, { resolve, reject }); 62 63 // Set timeout for invoke 64 setTimeout(() => { 65 if (this.responseHandlers.has(requestId)) { 66 this.responseHandlers.delete(requestId); 67 reject(new Error(`Invoke timeout: ${channel}`)); 68 } 69 }, 10000); 70 71 this.socket.emit("invoke", requestId, channel, ...args); 72 }); 73 } 74 75 on(channel: string, callback: EventCallback): void { 76 this.socket.on(channel, (...args: any[]) => { 77 // Mimic Electron's event object structure 78 const event = {}; 79 callback(event, ...args); 80 }); 81 } 82 } 83 84 // Detect environment and create appropriate adapter 85 function createAdapter(): IPCAdapter { 86 // Check if running in Electron 87 const isElectron = typeof window !== "undefined" && 88 typeof (window as any).process !== "undefined" && 89 (window as any).process.type === "renderer"; 90 91 if (isElectron) { 92 return new ElectronAdapter(); 93 } else { 94 return new WebAdapter(); 95 } 96 } 97 98 export const ipcAdapter = createAdapter();