import { contextBridge, ipcRenderer } from 'electron'; import { IPC, type ErrorReport, type KartenAnfrage, type KartenAntwort, type FileDialogFilter, type MenuCommand, type OpenFileResult, type SaveFileRequest, type SaveFileResult, type Verweisziel, } from '../shared/ipc'; /** * Bruecke zwischen Renderer und Hauptprozess. * * Es werden ausschliesslich genau umrissene Vorgaenge freigegeben - kein * allgemeiner Datei- oder Prozesszugriff. Der Altbestand reichte hier * `saveFile(pfad, daten)` und `readFile(pfad)` mit frei waehlbarem Pfad an den * Renderer durch. Zusammen mit den ungefilterten innerHTML-Einsetzungen im * Renderer liess sich damit ueber eine praeparierte Projektdatei eine beliebige * Datei auf die Platte schreiben. * * Jetzt gilt: Schreiben und Lesen sind nur fuer Pfade moeglich, die der Anwender * zuvor selbst in einem Dialog bestaetigt hat. Die Pruefung liegt im * Hauptprozess, nicht hier. * * Das Preload-Skript laeuft unter aktivierter Sandbox. Ein solches Skript kann * ausschliesslich `electron` einbinden - ein `require` auf eine Nachbardatei * schlaegt zur Laufzeit fehl ("module not found"), und zwar stumm: das Fenster * oeffnet sich, aber `window.lsaPlaner` fehlt und saemtliche Dateibefehle tun * nichts. Deshalb wird diese Datei fuer die Auslieferung mit esbuild zu einer * einzelnen, in sich geschlossenen Datei gebuendelt (siehe build:preload). * * KEIN `appVersion` IN DIESER BRUECKE - und das ist Absicht. Bis einschliesslich * 5.7.0 stand hier `process.env['LSA_APP_VERSION'] ?? '5.0.0'`. Gesetzt hat die * Umgebungsvariable nie jemand: kein Startbefehl, kein Bauschritt, kein * electron-builder-Eintrag. Die Bruecke meldete deshalb acht Fassungen lang * "5.0.0" - gemerkt hat es niemand, weil den Wert auch niemand gelesen hat. Die * Versionsangabe der Statusleiste kommt aus `__APP_VERSION__`, das vite.config.ts * aus package.json einsetzt; dort steht als Ueberschrift "Programmversion - eine * Quelle, nicht zwei", und genau die zweite Quelle war dieses Feld. * * Wird die Version im Renderer doch einmal aus dem Hauptprozess gebraucht, dann * aus `app.getVersion()` und ausdruecklich uebergeben - nicht aus einer * Umgebungsvariablen, die niemand fuellt, und nicht mit einer festen Zahl als * Rueckfall, die stillschweigend falsch wird. tests/electron/brueckeVersion.test.ts * haelt das fest. */ const bridge = { platform: 'electron' as const, saveFile(request: SaveFileRequest): Promise { return ipcRenderer.invoke(IPC.saveFile, { defaultName: String(request.defaultName), filters: request.filters, data: request.data, }) as Promise; }, openFile(filters: readonly FileDialogFilter[]): Promise { return ipcRenderer.invoke(IPC.openFile, filters) as Promise; }, writeFile(filePath: string, data: string | Uint8Array): Promise { return ipcRenderer.invoke(IPC.writeFile, { filePath: String(filePath), data, }) as Promise; }, readFile(filePath: string): Promise { return ipcRenderer.invoke(IPC.readFile, String(filePath)) as Promise; }, setDirty(dirty: boolean): void { ipcRenderer.send(IPC.setDirty, dirty === true); }, reportError(bericht: ErrorReport): void { ipcRenderer.send(IPC.logError, bericht); }, logPath(): Promise { return ipcRenderer.invoke(IPC.logPath) as Promise; }, fetchMapImage(anfrage: KartenAnfrage): Promise { return ipcRenderer.invoke(IPC.fetchMapImage, anfrage) as Promise; }, oeffneVerweis(ziel: Verweisziel): void { ipcRenderer.send(IPC.oeffneVerweis, ziel); }, onMenuCommand(handler: (command: MenuCommand) => void): () => void { const listener = (_event: unknown, command: MenuCommand): void => { handler(command); }; ipcRenderer.on(IPC.menuCommand, listener); // Der Renderer muss den Empfaenger wieder abmelden koennen; sonst // sammeln sich bei jedem Neuaufbau weitere an. return () => { ipcRenderer.removeListener(IPC.menuCommand, listener); }; }, onBeforeClose(handler: () => Promise): void { // Nur ein Empfaenger, auch wenn die Oberflaeche neu aufgebaut wird. ipcRenderer.removeAllListeners(IPC.requestClose); ipcRenderer.on(IPC.requestClose, () => { void handler() .then((allow) => { ipcRenderer.send(IPC.closeResponse, allow === true); }) .catch(() => { // Im Zweifel nicht schliessen - lieber nachfragen als Daten verlieren. ipcRenderer.send(IPC.closeResponse, false); }); }); }, }; contextBridge.exposeInMainWorld('lsaPlaner', bridge);