Фикс: дубликат CallKit вызова, disconnect recovery, WebRTC packet buffering и E2EE rebind loop

This commit is contained in:
2026-04-02 15:29:46 +05:00
parent 4be6761492
commit de0818fe69
10 changed files with 863 additions and 295 deletions

View File

@@ -95,6 +95,10 @@ final class ProtocolManager: @unchecked Sendable {
private var webRTCHandlers: [UUID: (PacketWebRTC) -> Void] = [:]
private var iceServersHandlers: [UUID: (PacketIceServers) -> Void] = [:]
/// Background task to keep WebSocket alive during brief background periods (active call).
/// iOS gives ~30s; enough for the call to survive app switching / notification interactions.
private var callBackgroundTask: UIBackgroundTaskIdentifier = .invalid
// Saved credentials for auto-reconnect
private var savedPublicKey: String?
private var savedPrivateHash: String?
@@ -185,6 +189,16 @@ final class ProtocolManager: @unchecked Sendable {
func forceReconnectOnForeground() {
guard savedPublicKey != nil, savedPrivateHash != nil else { return }
// During an active call the WebSocket may still be alive (background task
// keeps the process running for ~30s). Tearing it down would break signaling
// and trigger server re-delivery of .call causing endCallBecauseBusy.
// If the connection is authenticated, trust it and skip reconnect.
if CallManager.shared.uiState.phase != .idle,
connectionState == .authenticated {
Self.logger.info("⚡ Foreground reconnect skipped — active call, WS authenticated")
return
}
// Android parity: skip if handshake or device verification is in progress.
// These are active flows that should not be interrupted.
switch connectionState {
@@ -209,6 +223,25 @@ final class ProtocolManager: @unchecked Sendable {
client.forceReconnect()
}
// MARK: - Call Background Task
/// Keeps the process alive during active calls so WebSocket survives brief background.
func beginCallBackgroundTask() {
guard callBackgroundTask == .invalid else { return }
callBackgroundTask = UIApplication.shared.beginBackgroundTask(withName: "RosettaCall") { [weak self] in
self?.endCallBackgroundTask()
}
Self.logger.info("📞 Background task started for call")
}
func endCallBackgroundTask() {
guard callBackgroundTask != .invalid else { return }
let task = callBackgroundTask
callBackgroundTask = .invalid
UIApplication.shared.endBackgroundTask(task)
Self.logger.info("📞 Background task ended for call")
}
/// Android parity: `reconnectNowIfNeeded()` if already in an active state,
/// skip reconnect. Otherwise reset backoff and connect immediately.
func reconnectIfNeeded() {