// Detects whether the app is running inside the VNCmail+ (Bulwark) Electron // desktop shell and wraps the native notification bridge that // electron/preload.ts exposes via contextBridge. Mirrors how lib/web-push.ts // mirrors the React Native push flow - same idea, different native API: // PushManager/service-worker there, Electron's own Notification API here. // // Web/PWA deployments never get `window.vnc` at all (contextBridge only // exists inside the Electron shell), so `isElectronShell()` is false there // and callers should keep using the lib/web-push.ts + public/sw.js path. 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; } export interface VncElectronBridge { isElectron: true; showNotification: ( title: string, options?: ShowNotificationOptions, ) => Promise; wsConnect: (url: string, authHeader: string) => Promise; wsSend: (id: string, data: string) => Promise; wsClose: (id: string) => Promise; onWsMessage: (callback: (event: WsMessageEvent) => void) => () => void; } declare global { interface Window { vnc?: VncElectronBridge; } } export function isElectronShell(): boolean { return typeof window !== "undefined" && window.vnc?.isElectron === true; } /** * Shows a notification via Electron's native Notification API when running * inside the desktop shell. Resolves to false (never throws) when not * running in Electron, or when the main process reports notifications * unsupported on this OS/session - callers can fall back to the * service-worker push path (lib/web-push.ts) in that case. */ export async function showElectronNotification( title: string, options?: ShowNotificationOptions, ): Promise { if (!isElectronShell()) return false; const result = await window.vnc!.showNotification(title, options); return result.shown; }