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)
65 lines
2.1 KiB
TypeScript
65 lines
2.1 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.
|
|
|
|
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<ShowNotificationResult>;
|
|
wsConnect: (url: string, authHeader: string) => Promise<string>;
|
|
wsSend: (id: string, data: string) => Promise<boolean>;
|
|
wsClose: (id: string) => Promise<void>;
|
|
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<boolean> {
|
|
if (!isElectronShell()) return false;
|
|
const result = await window.vnc!.showNotification(title, options);
|
|
return result.shown;
|
|
}
|