feat(calls): CallKit kill-switch + fix ~1s re-ring on cancel
MIC BROKEN with CallKit: a CallKit call reserves the microphone, so the WebView's WebRTC can't capture it — calls are unusable until native LiveKit media lands. Add a server kill-switch (CALLKIT_ENABLED, default OFF) so CallKit can be flipped without an app rebuild: /api/meetings/config now returns callkit; setupNativeCall bails when off (-> WebView calls, mic works); push.js only sends VoIP/CallKit pushes when enabled. Deploying with the flag unset immediately restores working WebView calls. RE-RING: a late cancel push for an already-declined call hit the plugin's 'unknown uuid' path and re-reported a fresh incoming call (~1s re-ring). Track endedCalls and make a late cancel for an already-ended call a no-op. Server part deploys now (no rebuild); plugin re-ring fix ships with the native build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,8 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
|
||||
private var voipToken: String = ""
|
||||
// callUUID -> the call's data (room, kind, callerId, …) so answer/end can hand it back to JS.
|
||||
private var calls: [UUID: [String: Any]] = [:]
|
||||
// Calls we've already ended locally — so a LATE cancel push doesn't re-report (which briefly re-rang).
|
||||
private var endedCalls = Set<UUID>()
|
||||
|
||||
override public func load() {
|
||||
let config = CXProviderConfiguration(localizedName: "Biz Connect")
|
||||
@@ -98,6 +100,7 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
|
||||
private func requestEnd(_ uuid: UUID) {
|
||||
callController.request(CXTransaction(action: CXEndCallAction(call: uuid))) { _ in }
|
||||
calls.removeValue(forKey: uuid)
|
||||
endedCalls.insert(uuid)
|
||||
}
|
||||
|
||||
// MARK: - PushKit (VoIP)
|
||||
@@ -125,10 +128,16 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
|
||||
if calls[uuid] != nil {
|
||||
provider?.reportCall(with: uuid, endedAt: Date(), reason: .remoteEnded)
|
||||
calls.removeValue(forKey: uuid)
|
||||
endedCalls.insert(uuid)
|
||||
completion()
|
||||
} else if endedCalls.contains(uuid) {
|
||||
// Already ended locally (we declined/hung up) — do NOT re-report; that caused the ~1s re-ring.
|
||||
completion()
|
||||
} else {
|
||||
// Never saw the invite — iOS still requires a reported call for this push, so report then end.
|
||||
let u = CXCallUpdate()
|
||||
u.remoteHandle = CXHandle(type: .generic, value: "Call")
|
||||
endedCalls.insert(uuid)
|
||||
provider?.reportNewIncomingCall(with: uuid, update: u) { [weak self] _ in
|
||||
self?.provider?.reportCall(with: uuid, endedAt: Date(), reason: .remoteEnded)
|
||||
completion()
|
||||
@@ -158,6 +167,7 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
|
||||
|
||||
public func providerDidReset(_ provider: CXProvider) {
|
||||
calls.removeAll()
|
||||
endedCalls.removeAll()
|
||||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
|
||||
@@ -172,6 +182,7 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
|
||||
data["callUUID"] = action.callUUID.uuidString
|
||||
notifyListeners("endCall", data: data)
|
||||
calls.removeValue(forKey: action.callUUID)
|
||||
endedCalls.insert(action.callUUID)
|
||||
action.fulfill()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user