diff --git a/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift b/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift index 6961d8a..49c4a9d 100644 --- a/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift +++ b/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift @@ -211,27 +211,21 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega // reporting a NEW incoming call is what caused the "rings back for a second" blip on a call we've // already handled. So: if (dict["type"] as? String) == "cancel" { - // (a) already ended here → nothing to do (re-reporting would blip). - if endedCalls.contains(uuid) { completion(); return } - // (b) a call we already know (still ringing OR active) → end it WITHOUT reporting a new incoming, - // so there's no blip. reportCall(endedAt:) cleanly dismisses the CallKit UI. - if calls[uuid] != nil || activeUUID == uuid { - var ev = calls[uuid] ?? [:]; ev["callUUID"] = uuid.uuidString - calls.removeValue(forKey: uuid) - endedCalls.insert(uuid) - if activeUUID == uuid { disconnectRoom(); activeUUID = nil } - provider?.reportCall(with: uuid, endedAt: Date(), reason: .remoteEnded) - notifyListeners("endCall", data: ev) // if it was active, leave the meeting window too - completion() - return - } - // (c) unknown call (this very push relaunched the app) → we MUST report, then immediately end. - // A tiny unavoidable blip, but only in this rare cold case. + // iOS 13+ TERMINATES the app if a VoIP push doesn't result in reportNewIncomingCall before + // completion() — that's the crash after several calls (my earlier "no-blip" optimization SKIPPED + // the report for known/ended calls, which iOS kills for). So ALWAYS report, then immediately end: + // * uuid already ringing/active → the report errors harmlessly (NO second ring), reportCall ends it. + // * late/duplicate cancel (done) → a brief, unavoidable blip (acceptable; a crash is not). let u = CXCallUpdate() u.remoteHandle = CXHandle(type: .generic, value: (dict["callerName"] as? String) ?? "Call") + let wasActive = (activeUUID == uuid) + var ev = calls[uuid] ?? [:]; ev["callUUID"] = uuid.uuidString + calls.removeValue(forKey: uuid) endedCalls.insert(uuid) + if wasActive { disconnectRoom(); activeUUID = nil } provider?.reportNewIncomingCall(with: uuid, update: u) { [weak self] _ in self?.provider?.reportCall(with: uuid, endedAt: Date(), reason: .remoteEnded) + if wasActive { self?.notifyListeners("endCall", data: ev) } // active call cancelled → leave the meeting completion() } return