vojo/src/app/plugins/call/callForegroundService.ts

66 lines
2.9 KiB
TypeScript

// Typed wrapper around the CallForegroundService Capacitor plugin.
//
// The native service (CallForegroundService.java + CallForegroundPlugin.java)
// holds an Android foreground service with foregroundServiceType="microphone"
// for the duration of an active DM call. Without it, Android 14+ revokes the
// mic AppOp and applies background data firewall when the app goes to
// background (e.g. screen lock), killing the call.
//
//
import { registerPlugin } from '@capacitor/core';
import { isAndroidPlatform } from '../../utils/capacitor';
export interface IncomingRingUpsert {
eventId: string;
roomId: string;
callerName?: string;
// Milliseconds since epoch of the sender's origin timestamp (as reported by
// content.sender_ts). Native uses it to compute the expiry alarm so a
// late-rendered entry doesn't live past true lifetime.
senderTs?: number;
// Ring lifetime in milliseconds (content.lifetime, falls back to 30s native-side).
lifetime?: number;
// Optional — used as google.message_id in the PendingIntent extras so
// Capacitor's pushNotificationActionPerformed fires on Answer tap. Left
// blank for JS-originated upserts; Java merges richer FCM data on seed.
messageId?: string;
}
interface CallForegroundServicePlugin {
start(options?: { title?: string; body?: string }): Promise<void>;
stop(): Promise<void>;
upsertIncomingRing(options: IncomingRingUpsert): Promise<void>;
removeIncomingRing(options: { eventId: string }): Promise<void>;
}
const plugin = registerPlugin<CallForegroundServicePlugin>('CallForegroundService');
// Android-only — the native implementation lives in CallForegroundService.java
// and CallForegroundPlugin.java. Gating on platform here (not just isNativePlatform)
// avoids calling a non-existent plugin path on iOS or any future native target.
export const callForegroundService = {
start(options?: { title?: string; body?: string }): Promise<void> {
if (!isAndroidPlatform()) return Promise.resolve();
return plugin.start(options);
},
stop(): Promise<void> {
if (!isAndroidPlatform()) return Promise.resolve();
return plugin.stop();
},
// Add/refresh a live incoming ring in the native registry. Called from the
// incomingCallsAtom sync effect whenever a key lands in the atom (happy
// path). Idempotent with any prior FCM seed for the same eventId —
// (atom-sync cleanup + native registry bridge).
upsertIncomingRing(entry: IncomingRingUpsert): Promise<void> {
if (!isAndroidPlatform()) return Promise.resolve();
return plugin.upsertIncomingRing(entry);
},
// Drop a ring from the native registry and tombstone its eventId so a late
// FCM/sync re-seed within ring lifetime cannot resurrect it. Called from
// atom REMOVE, suppress paths, and the decline-first branch.
removeIncomingRing(eventId: string): Promise<void> {
if (!isAndroidPlatform()) return Promise.resolve();
return plugin.removeIncomingRing({ eventId });
},
};