// Die Brücke (Preload) — läuft im Fenster-Prozess mit contextIsolation. // Sie reicht GENAU DREI Dinge in die Seite, mehr nicht: // 1. den MessagePort zum Kern (Muster aus der Electron-Doku // »MessagePorts in Electron«: window.postMessage mit Transfer über die // Kontextgrenze — contextBridge kann keine Ports übertragen) // 2. den Haupt-Status (Kanal 'haupt-status') // 3. rippy.melden(art) für kurze Rückmeldungen ans Haupt // … plus die Windows-Dialoge und Shell-Wege, die nur das Haupt öffnen kann // (Ordner/Datei wählen, Ordner im Explorer zeigen, Protokoll öffnen). // // Beides wird GEPUFFERT: Das Haupt schickt Port und Status direkt nach // did-finish-load — ob die React-Seite ihre Horcher da schon registriert // hat, ist Timing. Ein Port, der vor dem Horcher eintrifft, wäre sonst // stumm verloren (R2: nichts verlieren, nur weil eine Nachricht früh kam). import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron' let seiteBereit = false const wartendePorts: IpcRendererEvent[] = [] ipcRenderer.on('kern-port', (ereignis) => { if (seiteBereit) { window.postMessage({ art: 'kern-port' }, '*', ereignis.ports) } else { wartendePorts.push(ereignis) } }) let letzterStatus: unknown = null const statusHorcher = new Set<(status: unknown) => void>() ipcRenderer.on('haupt-status', (_ereignis, status: unknown) => { letzterStatus = status for (const horcher of statusHorcher) horcher(status) }) contextBridge.exposeInMainWorld('rippy', { /** Die Seite meldet: Mein 'message'-Horcher steht — gepufferte Ports jetzt. */ bereit(): void { seiteBereit = true for (const ereignis of wartendePorts.splice(0)) { window.postMessage({ art: 'kern-port' }, '*', ereignis.ports) } }, /** Haupt-Status abonnieren; der letzte bekannte Stand kommt sofort. */ aufStatus(horcher: (status: unknown) => void): void { statusHorcher.add(horcher) if (letzterStatus !== null) horcher(letzterStatus) }, /** Kurze Rückmeldung ans Haupt (z. B. 'kern-pong' im Smoke-Beweis). */ melden(art: string): void { ipcRenderer.send('fenster-meldung', String(art)) }, /** Echter Windows-Ordnerdialog (§ 7). null = abgebrochen. */ ordnerWaehlen(titel: string): Promise { return ipcRenderer.invoke('ordner-waehlen', String(titel)) as Promise }, /** Echter Windows-Dateidialog für Programme (5.5.0: Werkzeuge → Durchsuchen). */ dateiWaehlen(titel: string): Promise { return ipcRenderer.invoke('datei-waehlen', String(titel)) as Promise }, /** Einen Ordner im Explorer zeigen (5.5.0). Liefert '' oder den Fehler. */ ordnerOeffnen(pfad: string): Promise { return ipcRenderer.invoke('ordner-oeffnen', String(pfad)) as Promise }, /** Den Protokoll-Ordner im Explorer zeigen (5.5.0). */ protokollOeffnen(): Promise { return ipcRenderer.invoke('protokoll-oeffnen') as Promise }, /** Autostart an/aus (§ 8) — liefert den tatsächlichen Zustand zurück. */ autostartSetzen(an: boolean): Promise { return ipcRenderer.invoke('autostart-setzen', an === true) as Promise }, /** Eigene Titelleiste (§ 4.2): minimieren | maximieren | schliessen. * „schliessen" versteckt nur — Rippy lebt im Tray weiter. */ fensterSteuern(was: 'minimieren' | 'maximieren' | 'schliessen'): Promise { return ipcRenderer.invoke('fenster-steuern', String(was)) as Promise }, /** „Jetzt prüfen" (§ 6.8) — das Ergebnis kommt über den Haupt-Status. */ updatePruefen(): Promise { return ipcRenderer.invoke('update-pruefen') as Promise }, /** „Jetzt neu starten und installieren" — antwortet mit Grund, wenn nicht. */ updateInstallieren(): Promise<{ ok: boolean; grund: string }> { return ipcRenderer.invoke('update-installieren') as Promise<{ ok: boolean; grund: string }> }, })