66 lines
2.9 KiB
TypeScript
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 });
|
|
},
|
|
};
|