vojo/src/app/hooks/useAndroidCallForegroundSync.ts

53 lines
2.2 KiB
TypeScript

// Keep an Android foreground service alive while a DM call is actively joined.
//
// Lifecycle is keyed to the widget's JoinCall signal (via useCallJoined), not
// the mere presence of callEmbedAtom. Rationale:
//
// 1. RECORD_AUDIO runtime grant. By the time JoinCall fires, Element Call
// inside the iframe has already called getUserMedia, which prompted the
// OS permission dialog (if needed) and user granted. Starting the FGS
// earlier (during `preparing`, before getUserMedia) means RECORD_AUDIO
// may not be granted yet — and on API 34+ startForeground with
// TYPE_MICROPHONE throws SecurityException without it, while a
// fallback to TYPE_NONE is not a valid subset of the manifest-declared
// `microphone` service type. Gating on joined sidesteps the whole
// fallback question.
//
// 2. No ghost notification during preparingError. If the widget fails to
// load, JoinCall never fires, the service never starts, nothing to
// clean up.
//
// Tradeoff: a tiny (1-3s) window between embed creation and JoinCall has no
// retention. The call isn't actually live in that window — media hasn't
// started — so lock-screen there is a non-event; user can retry.
//
// Android-only.
import { useEffect } from 'react';
import { useAtomValue } from 'jotai';
import { callEmbedAtom } from '../state/callEmbed';
import { callForegroundService } from '../plugins/call/callForegroundService';
import { useCallJoined } from './useCallEmbed';
import { isAndroidPlatform } from '../utils/capacitor';
export const useAndroidCallForegroundSync = (): void => {
const callEmbed = useAtomValue(callEmbedAtom);
const joined = useCallJoined(callEmbed);
useEffect(() => {
if (!isAndroidPlatform()) return undefined;
if (!joined) return undefined;
callForegroundService.start({ title: 'Активный звонок' }).catch((err: unknown) => {
// eslint-disable-next-line no-console
console.warn('[call-fgs] start failed', err);
});
return () => {
callForegroundService.stop().catch((err: unknown) => {
// eslint-disable-next-line no-console
console.warn('[call-fgs] stop failed', err);
});
};
}, [joined]);
};