From 9dc253143eb66eca3a7d22efc62ff22fb7e1fe3f Mon Sep 17 00:00:00 2001 From: sravan Date: Wed, 29 Jul 2026 20:44:09 +0530 Subject: [PATCH] fix(ios): keep CallKit call active on answer so the app foregrounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answering did not open the app: ending the CallKit call immediately (to free the mic) made iOS cancel the app launch before it foregrounded. Keep the call ACTIVE on answer — fulfilling an active-call answer is what foregrounds/unlocks the app — and fire the answerCall event so the WebView joins. didActivate stays a no-op (don't fight WebKit's mic). The CallKit call is ended later when the WebView call ends. Co-Authored-By: Claude Opus 4.8 --- .../NativeCallPlugin/NativeCallPlugin.swift | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift b/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift index 4c5ab10..174bab5 100644 --- a/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift +++ b/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift @@ -175,16 +175,12 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega var data: [String: Any] = calls[uuid] ?? [:] data["callUUID"] = uuid.uuidString action.fulfill() - // RING-ONLY MODEL: CallKit was used purely for the incoming ring. End the CallKit call NOW so iOS - // RELEASES the microphone — an active CallKit call reserves the mic and the WebView's WebRTC would get - // a dead mic. Then hand the call to the WebView, giving iOS a beat to tear down the CallKit audio - // session before the WebView grabs the mic. Background audio is preserved by the 'voip' background mode. - provider.reportCall(with: uuid, endedAt: Date(), reason: .remoteEnded) - calls.removeValue(forKey: uuid) - endedCalls.insert(uuid) - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in - self?.notifyListeners("answerCall", data: data) - } + // Keep the CallKit call ACTIVE — fulfilling the answer for an ACTIVE call is what foregrounds/unlocks + // the app and gives it in-call context (ending it here made iOS cancel the app launch → the app never + // opened). The WebView carries the media; we deliberately do NOT reconfigure the audio session + // (didActivate is a no-op) so we don't fight WebKit's WebRTC mic. The CallKit call is ended later when + // the WebView call ends (the dm-call/group-call inactive event → JS calls endCall()). + notifyListeners("answerCall", data: data) } public func provider(_ provider: CXProvider, perform action: CXEndCallAction) {