Phase 1 step 3 of VNCprodbuild. electron/preload.ts's contextBridge now
exposes window.vnc.showNotification(title, options), routed via
ipcRenderer.invoke("vnc:show-notification") to a new ipcMain.handle in
electron/main.ts that calls Electron's own Notification API. This is the
desktop shell's native notification path - it sits alongside, not in place
of, the browser/PWA's service-worker push path (public/sw.js's push/
notificationclick handlers + lib/web-push.ts), which is untouched.
lib/electron-bridge.ts gives the renderer a `isElectronShell()` +
`showElectronNotification()` wrapper so app code can detect the shell and
use the native path instead of/alongside SW push - not wired to any real
mail-delivery trigger yet, that's Phase 1 steps 4-6 (JMAP realtime
capability investigation, the background/foreground strategy decision, and
implementing it).
Extended e2e/electron-smoke.spec.ts to prove the IPC plumbing actually
fires end-to-end: calls window.vnc.showNotification from the renderer and
asserts the round-trip resolves (not that a real OS toast appears - not
observable in CI). Verified locally: the call resolves {"shown":true} on
this machine, confirming it genuinely reaches Electron's Notification API
and back, not just that window.vnc exists.
Also fixes a real bug caught by this step's typecheck: the smoke test's
Playwright Page variable was named `window`, shadowing the DOM global
inside every evaluate() callback and silently breaking their types. Renamed
to `appWindow`.
All 4 smoke-test assertions green: npm run build:electron && npm run
test:electron.
56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
// 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.
|
|
// Wiring this bridge up to real mail-delivery events (JMAP WebSocket push
|
|
// vs. polling) is a separate, later decision - this module is only the
|
|
// plumbing.
|
|
|
|
export interface ShowNotificationOptions {
|
|
body?: string;
|
|
tag?: string;
|
|
}
|
|
|
|
export interface ShowNotificationResult {
|
|
shown: boolean;
|
|
}
|
|
|
|
export interface VncElectronBridge {
|
|
isElectron: true;
|
|
showNotification: (
|
|
title: string,
|
|
options?: ShowNotificationOptions,
|
|
) => Promise<ShowNotificationResult>;
|
|
}
|
|
|
|
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<boolean> {
|
|
if (!isElectronShell()) return false;
|
|
const result = await window.vnc!.showNotification(title, options);
|
|
return result.shown;
|
|
}
|