825160ee46
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>
67 lines
1.9 KiB
TypeScript
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);
|
|
}
|
|
}
|