Phase 3 (security): - P3.1: Feature gate server-side enforcement (403 on disabled features) - P3.2: Unified auth error interceptor (401→logout) - P3.3: Store-level state isolation via StoreSnapshot contract (added message-list-tabs + task stores to snapshot/restore cycle) - P3.4: Push event bus extraction — email-store no longer imports calendar/contact/filter/file stores directly - P1.3: Auth localStorage AES-GCM encryption via custom Zustand adapter Phase 4 (polish): - P4.1: Offline write queue — pending operations in localStorage, auto-retry on reconnect, offline-queue-indicator banner - P4.2: Identity spoofing — fromOverrideEmail domain validation - P4.3: WebSocket push for Electron via main-process IPC bridge (ws package with Authorization headers)
58 lines
2.0 KiB
TypeScript
58 lines
2.0 KiB
TypeScript
// 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<ShowNotificationResult> =>
|
|
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<string> =>
|
|
ipcRenderer.invoke("vnc:ws-connect", { url, authHeader }),
|
|
|
|
wsSend: (id: string, data: string): Promise<boolean> =>
|
|
ipcRenderer.invoke("vnc:ws-send", { id, data }),
|
|
|
|
wsClose: (id: string): Promise<void> =>
|
|
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); };
|
|
},
|
|
});
|