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>
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
getOwnProfile,
|
||||
signOut as supabaseSignOut,
|
||||
type Profile,
|
||||
touchDeviceLastSeen,
|
||||
updateOwnProfile,
|
||||
} from '@chat-app/shared/auth';
|
||||
import { changeLocale, isSupportedLocale } from '@chat-app/shared/i18n';
|
||||
@@ -14,11 +15,13 @@ import {
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { findExistingDevice } from '../lib/device';
|
||||
import { PRESENCE_HEARTBEAT_MS } from '../lib/presence';
|
||||
import { setSecretStoreUser } from '../lib/secretStore';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { registerWebPush } from '../lib/webPush';
|
||||
@@ -46,6 +49,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [profile, setProfile] = useState<Profile | null>(null);
|
||||
const [device, setDevice] = useState<DeviceRecord | null>(null);
|
||||
const [deviceLookupDone, setDeviceLookupDone] = useState(false);
|
||||
const autoOnlineUserRef = useRef<string | null>(null);
|
||||
|
||||
// Initial session + auth subscription. We verify the cached JWT against the
|
||||
// server (via getUser) once on mount. Only purge the session on an
|
||||
@@ -154,8 +158,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
// request usually slips through; the next page load corrects state if it
|
||||
// didn't.
|
||||
useEffect(() => {
|
||||
if (!session || !profile) return;
|
||||
if (profile.presenceState === 'offline') {
|
||||
if (!session) {
|
||||
autoOnlineUserRef.current = null;
|
||||
return;
|
||||
}
|
||||
if (!profile) return;
|
||||
const shouldApplyInitialAutoOnline = autoOnlineUserRef.current !== session.user.id;
|
||||
autoOnlineUserRef.current = session.user.id;
|
||||
if (profile.presenceState === 'offline' && shouldApplyInitialAutoOnline) {
|
||||
void updateOwnProfile(supabase, { presenceState: 'online' })
|
||||
.then(() => refreshProfile())
|
||||
.catch((err: unknown) => {
|
||||
@@ -165,10 +175,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const onLeave = () => {
|
||||
// Skip if user explicitly chose a non-online state — they probably
|
||||
// want to look unavailable on next reconnect too.
|
||||
if (
|
||||
profile.presenceState !== 'online' &&
|
||||
profile.presenceState !== 'offline'
|
||||
) {
|
||||
if (profile.presenceState !== 'online' && profile.presenceState !== 'offline') {
|
||||
return;
|
||||
}
|
||||
void updateOwnProfile(supabase, { presenceState: 'offline' }).catch(() => {});
|
||||
@@ -181,7 +188,36 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
};
|
||||
}, [session, profile, refreshProfile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!session || !device?.id) return;
|
||||
|
||||
const touch = () => {
|
||||
void touchDeviceLastSeen(supabase, device.id).catch((err: unknown) => {
|
||||
console.warn('presence heartbeat failed', err);
|
||||
});
|
||||
};
|
||||
const touchWhenVisible = () => {
|
||||
if (document.visibilityState === 'visible') touch();
|
||||
};
|
||||
|
||||
touch();
|
||||
const heartbeat = window.setInterval(touch, PRESENCE_HEARTBEAT_MS);
|
||||
window.addEventListener('focus', touch);
|
||||
window.addEventListener('online', touch);
|
||||
document.addEventListener('visibilitychange', touchWhenVisible);
|
||||
|
||||
return () => {
|
||||
window.clearInterval(heartbeat);
|
||||
window.removeEventListener('focus', touch);
|
||||
window.removeEventListener('online', touch);
|
||||
document.removeEventListener('visibilitychange', touchWhenVisible);
|
||||
};
|
||||
}, [session, device?.id]);
|
||||
|
||||
const signOut = useCallback(async () => {
|
||||
await updateOwnProfile(supabase, { presenceState: 'offline' }).catch((err: unknown) => {
|
||||
console.warn('offline update before sign-out failed', err);
|
||||
});
|
||||
await supabaseSignOut(supabase);
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user