Files
ChatApp/apps/desktop/src/lib/osNotify.ts
T
byGalax 825160ee46 feat(desktop): port v0.11.4-v0.15.2 from Tauri to Electron + Discord-parity audio (v0.16.0)
Chronological port of every Tauri release commit (v0.11.4 -> v0.15.2)
into the Electron rebuild, plus Discord-style audio handling that goes
beyond the Tauri original.

Highlights:
  - All 17 Tauri release commits ported (audio fixes, custom notification
    sound, Discord-style chat UX, profile banner, changelog page,
    Discord-parity call UX, screen-share echo + re-watch UX, audio-loop
    fix).
  - Native napi-rs audio-loopback addon with WASAPI process-loopback:
      * EXCLUDE_TARGET_PROCESS_TREE for full-screen shares -> peers
        never hear themselves echoed back through the capture.
      * INCLUDE_TARGET_PROCESS_TREE for window shares -> only the
        picked window's audio is captured, not the whole OS mixer
        (Discord parity).
      * HWND -> PID resolution via Win32 GetWindowThreadProcessId.
  - Discord-style screen-source picker (thumbnail grid, screens vs
    apps tabs, live-refreshing thumbnails).
  - Hash routing fix for packaged builds (file:// can't resolve
    BrowserRouter paths).
  - Tauri sources removed (apps/desktop/src-tauri).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:35:01 +02:00

67 lines
1.9 KiB
TypeScript

// OS notifications via the Electron preload bridge. The exported API
// stays identical to the Tauri-era version so callers don't change.
//
// Permission under Electron is implicit ('granted' always). The legacy
// ASKED marker in localStorage is kept so we don't re-prompt after a
// previously-denied run; we just fast-succeed now.
import { isTauriRuntime } from './globalShortcut';
let permissionChecked = false;
let permissionGranted = false;
const ASKED_KEY = 'chatapp.notif.asked';
function writeAskedMarker(): void {
try {
window.localStorage.setItem(ASKED_KEY, '1');
} catch {
/* quota / private mode */
}
}
export async function ensureNotificationPermission(): Promise<boolean> {
if (permissionChecked) return permissionGranted;
permissionChecked = true;
if (!isTauriRuntime()) {
permissionGranted = false;
return false;
}
try {
const state = await window.electronAPI.getNotificationPermission();
permissionGranted = state === 'granted';
writeAskedMarker();
} catch (err: unknown) {
permissionGranted = false;
console.warn('notification permission check failed', err);
}
return permissionGranted;
}
export function isAppFocused(): boolean {
return typeof document !== 'undefined' && !document.hidden && document.hasFocus();
}
interface NotifyOpts {
title: string;
body?: string;
/** Force notification even when app is focused. Default: suppress if focused. */
force?: boolean;
}
export async function notify({ title, body, force = false }: NotifyOpts): Promise<void> {
if (!force && isAppFocused()) return;
if (!isTauriRuntime()) return;
const granted = await ensureNotificationPermission();
if (!granted) return;
try {
await window.electronAPI.notify({
title,
body: body ?? '',
silent: true,
});
} catch (err: unknown) {
console.error('notify failed', err);
}
}