// Preload script for the VNCmail+ desktop shell. Runs in an isolated // context with access to Node APIs, and exposes a minimal, explicit surface // to the renderer via contextBridge - the renderer never gets direct Node or // Electron access (contextIsolation + nodeIntegration: false, see main.ts). import { contextBridge, ipcRenderer } from "electron"; export interface ShowNotificationOptions { body?: string; tag?: string; } export interface ShowNotificationResult { shown: boolean; } export interface WsMessageEvent { id: string; type: "open" | "message" | "close" | "error"; data?: string; code?: number; message?: string; } contextBridge.exposeInMainWorld("vnc", { isElectron: true, // Routes to Electron's own Notification API in main.ts (ipcMain.handle // "vnc:show-notification"). This is the desktop shell's native // notification path - it does not replace lib/web-push.ts's Web Push // (VAPID) path, which is what the browser/PWA deployment still uses. showNotification: ( title: string, options?: ShowNotificationOptions, ): Promise => ipcRenderer.invoke("vnc:show-notification", title, options), // WebSocket bridge for JMAP-over-WebSocket (RFC 8887). The browser // WebSocket constructor cannot attach Authorization headers, so // connections go through the main process which controls headers. wsConnect: ( url: string, authHeader: string, ): Promise => ipcRenderer.invoke("vnc:ws-connect", { url, authHeader }), wsSend: (id: string, data: string): Promise => ipcRenderer.invoke("vnc:ws-send", { id, data }), wsClose: (id: string): Promise => ipcRenderer.invoke("vnc:ws-close", { id }), onWsMessage: (callback: (event: WsMessageEvent) => void): () => void => { const handler = (_event: Electron.IpcRendererEvent, data: WsMessageEvent) => callback(data); ipcRenderer.on("vnc:ws-message", handler); return () => { ipcRenderer.removeListener("vnc:ws-message", handler); }; }, });