80 lines
3.8 KiB
TypeScript
80 lines
3.8 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 {
|
|
// phoneCall — Telecom mode (Phase C): also tag the FGS with the phoneCall type
|
|
// so it matches the ring-time service and survives a process restart. Omitted
|
|
// / false keeps the legacy microphone-only behavior.
|
|
start(options?: { title?: string; body?: string; phoneCall?: boolean }): Promise<void>;
|
|
stop(): Promise<void>;
|
|
upsertIncomingRing(options: IncomingRingUpsert): Promise<void>;
|
|
removeIncomingRing(options: { eventId: string }): Promise<void>;
|
|
clearAllIncomingRings(): 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; phoneCall?: boolean }): 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 });
|
|
},
|
|
// Logout / session-loss sweep: tear down every live ring + the Telecom session
|
|
// + the foreground service. The JS logout path does window.location.reload(),
|
|
// which runs no React effect cleanup, and a cold-start ring may never have
|
|
// entered incomingCallsAtom — so the process-static native ring/Telecom/FGS
|
|
// state must be cleared explicitly before the reload or it outlives the
|
|
// session as an unactionable zombie call (RED-3).
|
|
clearAllIncomingRings(): Promise<void> {
|
|
if (!isAndroidPlatform()) return Promise.resolve();
|
|
return plugin.clearAllIncomingRings();
|
|
},
|
|
};
|