/ 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();