Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error receiving PushVoIP in the background IOS #583

Open
robsonvasquez opened this issue Sep 19, 2024 · 6 comments
Open

Error receiving PushVoIP in the background IOS #583

robsonvasquez opened this issue Sep 19, 2024 · 6 comments

Comments

@robsonvasquez
Copy link

I receive the PushVoIP notification normally on iOS when the app is in the foreground, but when it goes to the background or is terminated, I don’t receive the PushVoIP notification.

delegate:

import UIKit
import Flutter

import awesome_notifications
//import shared_preferences_ios
import shared_preferences_foundation

import CallKit
import AVFAudio
import PushKit
import flutter_callkit_incoming
import WebRTC

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate {
//var flutterEngine: FlutterEngine?

override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

if #available(iOS 10.0, *) {
  UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self)

SwiftAwesomeNotificationsPlugin.setPluginRegistrantCallback { registry in          
      SwiftAwesomeNotificationsPlugin.register(
        with: registry.registrar(forPlugin: "io.flutter.plugins.awesomenotifications.AwesomeNotificationsPlugin")!)          
      SharedPreferencesPlugin.register(
        with: registry.registrar(forPlugin: "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")!)
  }


//Setup VOIP
let mainQueue = DispatchQueue.main
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
voipRegistry.delegate = self
voipRegistry.desiredPushTypes = [PKPushType.voIP]

//Use if using WebRTC
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
    
  
return super.application(application, didFinishLaunchingWithOptions: launchOptions)

}

// Call back from Recent history
override func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

    guard let handleObj = userActivity.handle else {
        return false
    }
    
    guard let isVideo = userActivity.isVideo else {
        return false
    }
    let objData = handleObj.getDecryptHandle()
    let nameCaller = objData["nameCaller"] as? String ?? ""
    let handle = objData["handle"] as? String ?? ""
    let data = flutter_callkit_incoming.Data(id: UUID().uuidString, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
    //set more data...
    //data.nameCaller = nameCaller
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.startCall(data, fromPushKit: true)
    
    return super.application(application, continue: userActivity, restorationHandler: restorationHandler)
}

// Handle updated push credentials
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
    print(credentials.token)
    let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined()
    print(deviceToken)
    //Save deviceToken to your server
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)
}

func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
    print("didInvalidatePushTokenFor")
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("")
}

// Handle incoming pushes
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    print("didReceiveIncomingPushWith t")
    print(payload.dictionaryPayload)
    guard type == .voIP else { 
        return print("aqui")
    }
    
    // let id = payload.dictionaryPayload["id"] as? String ?? ""
    // let nameCaller = payload.dictionaryPayload["nameCaller"] as? String ?? ""
    // let handle = payload.dictionaryPayload["handle"] as? String ?? ""
    // let isVideo = payload.dictionaryPayload["isVideo"] as? Bool ?? false
    
    // let data = flutter_callkit_incoming.Data(id: id, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
    // //set more data
    // data.extra = ["user": "abc@123", "platform": "ios"]
    // //data.iconName = ...
    // //data.....
    // SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
    
    // //Make sure call completion()
    // DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    //     completion()
    // }

    let domain = payload.dictionaryPayload["domain"] as? String ?? ""
    let iceTimeout = payload.dictionaryPayload["iceTimeout"] as? String ?? ""
    let ring_time = payload.dictionaryPayload["ring_time"] as? String ?? ""
    let stun = payload.dictionaryPayload["stun"] as? String ?? ""
    let stunURL = payload.dictionaryPayload["stunURL"] as? String ?? ""
    let tcpPort = payload.dictionaryPayload["tcpPort"] as? String ?? ""
    let turn = payload.dictionaryPayload["turn"] as? String ?? ""
    let turnCredential = payload.dictionaryPayload["turnCredential"] as? String ?? ""
    let turnURL = payload.dictionaryPayload["turnURL"] as? String ?? ""
    let turnUsername = payload.dictionaryPayload["turnUsername"] as? String ?? ""
    let wsdomain = payload.dictionaryPayload["wsdomain"] as? String ?? ""
    let sip_username = payload.dictionaryPayload["sip_username"] as? String ?? ""
    let sip_password = payload.dictionaryPayload["sip_password"] as? String ?? ""
    let zona = payload.dictionaryPayload["zona"] as? String ?? ""
    let zone_id = payload.dictionaryPayload["zone_id"] as? String ?? ""
    let conta = payload.dictionaryPayload["conta"] as? String ?? ""
    let nameAccount = payload.dictionaryPayload["nameAccount"] as? String ?? ""

    // Cria um dicionário com todos os valores
    let arguments: [String: Any] = [
        "domain": domain,
        "iceTimeout": iceTimeout,
        "ring_time": ring_time,
        "stun": stun,
        "stunURL": stunURL,
        "tcpPort": tcpPort,
        "turn": turn,
        "turnCredential": turnCredential,
        "turnURL": turnURL,
        "turnUsername": turnUsername,
        "wsdomain": wsdomain,
        "sip_username": sip_username,
        "sip_password": sip_password,
        "zona": zona,
        "zone_id": zone_id,
        "conta": conta,
        "nameAccount": nameAccount
    ]

    // Chamar método Flutter aqui
    callFlutterMethod(method: "startSip", arguments: arguments)
}


// Func Call api for Accept
func onAccept(_ call: Call, _ action: CXAnswerCallAction) {
    let json = ["action": "ACCEPT", "data": call.data.toJSON()] as [String: Any]
    print("LOG: onAccept")
    self.performRequest(parameters: json) { result in
        switch result {
        case .success(let data):
            print("Received data: \(data)")
            //Make sure call action.fulfill() when you are done(connected WebRTC - Start counting seconds)
            action.fulfill()

        case .failure(let error):
            print("Error: \(error.localizedDescription)")
        }
    }
}

// Func Call API for Decline
func onDecline(_ call: Call, _ action: CXEndCallAction) {
    let json = ["action": "DECLINE", "data": call.data.toJSON()] as [String: Any]
    print("LOG: onDecline")
    self.performRequest(parameters: json) { result in
        switch result {
        case .success(let data):
            print("Received data: \(data)")
            //Make sure call action.fulfill() when you are done
            action.fulfill()

        case .failure(let error):
            print("Error: \(error.localizedDescription)")
        }
    }
}

// Func Call API for End
func onEnd(_ call: Call, _ action: CXEndCallAction) {
    let json = ["action": "END", "data": call.data.toJSON()] as [String: Any]
    print("LOG: onEnd")
    self.performRequest(parameters: json) { result in
        switch result {
        case .success(let data):
            print("Received data: \(data)")
            //Make sure call action.fulfill() when you are done
            action.fulfill()

        case .failure(let error):
            print("Error: \(error.localizedDescription)")
        }
    }
}

// Func Call API for TimeOut
func onTimeOut(_ call: Call) {
    let json = ["action": "TIMEOUT", "data": call.data.toJSON()] as [String: Any]
    print("LOG: onTimeOut")
    self.performRequest(parameters: json) { result in
        switch result {
        case .success(let data):
            print("Received data: \(data)")

        case .failure(let error):
            print("Error: \(error.localizedDescription)")
        }
    }
}

// Func Callback Toggle Audio Session
func didActivateAudioSession(_ audioSession: AVAudioSession) {
    //Use if using WebRTC
    //RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
    //RTCAudioSession.sharedInstance().isAudioEnabled = true
}

// Func Callback Toggle Audio Session
func didDeactivateAudioSession(_ audioSession: AVAudioSession) {
    //Use if using WebRTC
    //RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
    //RTCAudioSession.sharedInstance().isAudioEnabled = false
}

func performRequest(parameters: [String: Any], completion: @escaping (Result<Any, Error>) -> Void) {
    if let url = URL(string: "https://webhook.site/e32a591f-0d17-469d-a70d-33e9f9d60727") {
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        //Add header
        
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: parameters, options: [])
            request.httpBody = jsonData
        } catch {
            completion(.failure(error))
            return
        }
        
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let data = data else {
                completion(.failure(NSError(domain: "mobile.app", code: 0, userInfo: [NSLocalizedDescriptionKey: "Empty data"])))
                return
            }

            do {
                let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
                completion(.success(jsonObject))
            } catch {
                completion(.failure(error))
            }
        }
        task.resume()
    } else {
        completion(.failure(NSError(domain: "mobile.app", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
    }
}

// Função para chamar o método Flutter
func callFlutterMethod(method: String, arguments: [String: Any]) {
    // guard let flutterEngine = self.flutterEngine,
    // let flutterViewController = flutterEngine.viewController else {
    //     print("Flutter engine not initialized.")
    //     return
    // }

    print(method)

    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    
    let channel = FlutterMethodChannel(name: "flutter_callkit_channel_ios", binaryMessenger: controller.binaryMessenger)
    
    channel.invokeMethod(method, arguments: arguments) { result in
        print("Result from Flutter: \(String(describing: result))")
    }
}

}

@robsonvasquez robsonvasquez changed the title Error receiving PushVoIP in the background Error receiving PushVoIP in the background IOS Sep 19, 2024
@Essenbay
Copy link

Same issue

@pasanediri97
Copy link

same issue. any solutions?

@Essenbay
Copy link

Essenbay commented Sep 23, 2024

Only in terminated and background right?
"Apparently if you didn't have everything configured right, Apple will start to suspend your voip notifications and won't deliver."
Seems I handled some events wrong (while firsly configuring my app) and apple suspended my bundle.
Try to delete app and install again. Worked for me, spent whole day to figure this out.

@robsonvasquez
Copy link
Author

That was exactly my mistake. I ended up removing and reinstalling, and it worked!

But now my problem is accepting the incoming call. I’m receiving it, but when I answer, it throws an error.

@Essenbay
Copy link

Essenbay commented Sep 24, 2024

What error? Maybe you are passing not UUID value for id?
Check #571 (comment)

Also, make sure you configure pushRegistry for your notification type and requirements. Here’s an example of how mine is set up:

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    os_log("didReceiveIncomingPushWith", log: OSLog.default, type: .info)
    os_log("Payload received: %@", log: OSLog.default, type: .info, payload.dictionaryPayload.description)

    guard type == .voIP else { return }

    if let data = payload.dictionaryPayload["data"] as? [String: Any] {
        let uniqueID = UUID().uuidString
        let id = data["id"] as? String ?? ""
        let nameCaller = data["caller_name"] as? String ?? ""
        let handle = data["handle"] as? String ?? ""
        let isVideo = data["isVideo"] as? Bool ?? false

        os_log("ID: %{public}@", log: OSLog.default, type: .info, id)
        os_log("Caller Name: %{public}@", log: OSLog.default, type: .info, nameCaller)
        os_log("Handle: %{public}@", log: OSLog.default, type: .info, handle)
        os_log("Is Video: %d", log: OSLog.default, type: .info, isVideo ? 1 : 0)

        let callData = flutter_callkit_incoming.Data(id: uniqueID, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
        // Set additional data if necessary
        callData.extra = ["user": "abc@123", "platform": "ios", "caller_id": id, "nameCaller": nameCaller]

        // Show incoming call using CallKit
        SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(callData, fromPushKit: true)
    } else {
        os_log("Failed to parse data from payload")
    }

    // Make sure to call completion()
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        completion()
    }
}

Make sure the id you're passing is in UUID format, and confirm the payload structure matches what is expected.

@robsonvasquez
Copy link
Author

Currently, I’m receiving the VoIP notification. Upon receiving it, I register with my SIP server, and after registering and receiving the Invite, I want to display the notification that a call is incoming. However, it seems that I’m forced to show this notification in the function that handles receiving the VoIP notification, even though I haven’t received the call Invite yet. I want to register upon receiving the VoIP notification and only generate the incoming call notification after receiving the Invite, but it’s not working.

// Handle incoming pushes
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
print("didReceiveIncomingPushWith t")
print(payload.dictionaryPayload)
print(type)
guard type == .voIP else {return}

    // let id = payload.dictionaryPayload["uuid"] as? String ?? ""
    // let nameCaller = payload.dictionaryPayload["callerName"] as? String ?? ""
    // let handle = payload.dictionaryPayload["handle"] as? String ?? ""
    // let isVideo = payload.dictionaryPayload["isVideo"] as? Bool ?? false
    
    // let data = flutter_callkit_incoming.Data(id: id, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
    // //set more data
    // data.extra = ["user": "abc@123", "platform": "ios"]
    // //data.iconName = ...
    // //data.....
    //SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
    

    let domain = payload.dictionaryPayload["domain"] as? String ?? ""
    let iceTimeout = payload.dictionaryPayload["iceTimeout"] as? String ?? ""
    let ring_time = payload.dictionaryPayload["ring_time"] as? String ?? ""
    let stun = payload.dictionaryPayload["stun"] as? String ?? ""
    let stunURL = payload.dictionaryPayload["stunURL"] as? String ?? ""
    let tcpPort = payload.dictionaryPayload["tcpPort"] as? String ?? ""
    let turn = payload.dictionaryPayload["turn"] as? String ?? ""
    let turnCredential = payload.dictionaryPayload["turnCredential"] as? String ?? ""
    let turnURL = payload.dictionaryPayload["turnURL"] as? String ?? ""
    let turnUsername = payload.dictionaryPayload["turnUsername"] as? String ?? ""
    let wsdomain = payload.dictionaryPayload["wsdomain"] as? String ?? ""
    let sip_username = payload.dictionaryPayload["sip_username"] as? String ?? ""
    let sip_password = payload.dictionaryPayload["sip_password"] as? String ?? ""
    let zona = payload.dictionaryPayload["zona"] as? String ?? "teste"
    let zone_id = payload.dictionaryPayload["zone_id"] as? String ?? ""
    let conta = payload.dictionaryPayload["conta"] as? String ?? ""
    let nameAccount = payload.dictionaryPayload["nameAccount"] as? String ?? ""

    // Cria um dicionário com todos os valores
    let arguments: [String: Any] = [
        "domain": domain,
        "iceTimeout": iceTimeout,
        "ring_time": ring_time,
        "stun": stun,
        "stunURL": stunURL,
        "tcpPort": tcpPort,
        "turn": turn,
        "turnCredential": turnCredential,
        "turnURL": turnURL,
        "turnUsername": turnUsername,
        "wsdomain": wsdomain,
        "sip_username": sip_username,
        "sip_password": sip_password,
        "zona": zona,
        "zone_id": zone_id,
        "conta": conta,
        "nameAccount": nameAccount
    ]

    // Chamar método Flutter aqui
    callFlutterMethod(method: "startSip", arguments: arguments)

    //Make sure call completion()
    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
        completion()
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants