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

Handling SIP Calls and VoIP Notifications in React Native #950

Open
NomanUmar opened this issue Sep 4, 2024 · 0 comments
Open

Handling SIP Calls and VoIP Notifications in React Native #950

NomanUmar opened this issue Sep 4, 2024 · 0 comments

Comments

@NomanUmar
Copy link

NomanUmar commented Sep 4, 2024

Description:

I am working on a React Native project that integrates Linphone for SIP functionalities and uses CallKit and VoIP push notifications. I am encountering issues when the app is in a killed state. Here’s a detailed description of the problem:

Problem:

When the app is in a killed state, I receive a VoIP push notification, and RNCallKeep successfully reports a new incoming call. However, the SIP login is not performed, and no invite is received. This prevents the SIP call from being established.

Code Snippets:

PushKit Handler:

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    print(payload.dictionaryPayload)
    // Ensure payload data contains necessary fields
    guard let payloadData = payload.dictionaryPayload as? [String: Any],
          let aps = payloadData["aps"] as? [String: Any],
          let handle = payloadData["handle"] as? String,
          let callUUIDString = payloadData["callUUID"] as? String,
          let callUUID = UUID(uuidString: callUUIDString) else {
      completion()
      return
    }
    
    let uuid = UUID(uuidString: callUUIDString) ?? UUID()
    RNVoipPushNotificationManager.addCompletionHandler(callUUIDString, completionHandler: completion)
    RNVoipPushNotificationManager.didReceiveIncomingPush(with: payload, forType: type.rawValue)
    RNCallKeep.reportNewIncomingCall(callUUIDString, handle: handle, handleType: "generic", hasVideo: false, localizedCallerName: "\(handle)", supportsHolding: true, supportsDTMF: true, supportsGrouping: true, supportsUngrouping: true, fromPushKit: true, payload: nil)
    completion()
}

SIP Module in Swift:

import React
import AVFAudio
import linphonesw

@objc(SipModule)
class SipModule: RCTEventEmitter {
  private let factory = Factory.Instance
  private var core: Core
  private var listener = CoreDelegateStub()
  private var call: Call? {
    get {
      return core.currentCall ?? core.calls.first
    }
  }
    
  override static func moduleName() -> String! {
    return "SipModule"
  }
  
  override func supportedEvents() -> [String]! {
    return ["incomingReceived", "callReleased","connected","NOTIFICATION_ACTION","callAccepted"]
  }
  
  override static func requiresMainQueueSetup() -> Bool {
    return false
  }
  
  override init() {
#if DEBUG
    LoggingService.Instance.logLevel = LogLevel.Debug
#endif
    
    try! core = factory.createCore(configPath: nil, factoryConfigPath: nil, systemContext: nil)
    
    super.init()
    
    listener = CoreDelegateStub(
      onCallStateChanged: {
        (core: Core, call: Call, state: Call.State?, message: String) in
        switch(state) {
        case .IncomingReceived:
          print("incoming call")
          let payload = [
            "displayName": call.remoteAddress?.displayName,
            "username": call.remoteAddress?.username
          ]
          self.sendEvent(withName: "incomingReceived", body: payload)
        case .Released:
          print("released call")
          self.sendEvent(withName: "callReleased", body: [:])
        case .Connected:
          print("connected call")
          let payload = [
            "displayName": call.remoteAddress?.displayName,
            "username": call.remoteAddress?.username
          ]
          self.sendEvent(withName: "connected", body:payload)
        default:
          return
        }
      },
      onAccountRegistrationStateChanged: {
        (core: Core, account: Account, state: RegistrationState, message: String) in
        _ = message
        // If account has been configured correctly, we will go through Progress and Ok states
        // Otherwise, we will be Failed.
        NSLog("New registration state is \(state) for user id \( String(describing: account.params?.identityAddress?.asString()))\n")
        if (state == .Ok) {
          print("Loggedin")
        } else if (state == .Cleared) {
          print("error")
        }  else if (state == .Failed) {
          print(message)
        }
      }
    )
    
    core.addDelegate(delegate: listener)
  }
  
  @objc
  func login(_ username: String, password: String, domain: String) {
    do {
      let authInfo = try Factory.Instance.createAuthInfo(username: username, userid: nil,
                                                         passwd: password, ha1: nil, realm: nil,
                                                         domain: domain,algorithm: nil)
      let params = try core.createAccountParams()
      let identityAddress = try Factory.Instance.createAddress(addr: "sip:\(username)@\(domain)")
      try params.setIdentityaddress(newValue: identityAddress)
      params.registerEnabled = true
      
      let serverAddress = try Factory.Instance.createAddress(addr: "sip:\(domain)")
      try serverAddress.setTransport(newValue: .Udp)
      try params.setServeraddress(newValue: serverAddress)
      
      let account = try core.createAccount(params: params)
      
      core.addAuthInfo(info: authInfo)
      try core.addAccount(account: account)
      core.defaultAccount = account
      try core.start()
    } catch (let error) {
      print("~Error ===> \(error)")
    }
  }
  
  
  @objc
  func startCall(_ phone: String, domain: String) {
    do {
      let address = core.interpretUrl(url: "\(phone)@\(domain)", applyInternationalPrefix: true)
      
      guard let address = address else {
        return
      }
      try address.setDisplayname(newValue: "Caller")
      let params = try core.createCallParams(call: nil)
      let _ = core.inviteAddressWithParams(addr: address, params: params)
    } catch {
      
    }
  }
  
  @objc
  func answerCall() {
    do {
      try call?.accept()
    } catch {
      
    }
  }
  
  @objc
  func endCall() {
    DispatchQueue.main.async {
      do {
        if let call = self.call {
          try call.terminate()
        }
      } catch {
      }
    }
  }
  
  @objc
  func sendDtmf(_ dtmf: String) {
    do {
      try call?.sendDtmf(dtmf: dtmf.utf8CString[0])
    } catch {
      
    }
  }
  
  @objc
  func changeAudioOutput(_ output: Int) {
    call?.outputAudioDevice = core.audioDevices.first { device in
      device.type.rawValue == output
    }
  }
  
  @objc
  func toggleMicrophone() {
    core.micEnabled = !core.micEnabled
  }
  
  @objc
  func pauseCall() {
    do {
      try call?.pause()
    } catch {
      
    }
  }
  
  @objc
  func resumeCall() {
    do {
      try call?.resume()
    } catch {
      NSLog(error.localizedDescription)
    }
  }
  
  @objc 
  func printStatement(){
    print("print call from react native")
  }
}

Steps to Reproduce:

Ensure the app is killed (not running in the background).
Send a VoIP push notification to the app.
Observe that the notification is received and CallKit displays the incoming call UI.
SIP login does not occur, and no invite is processed.
Expected Behavior:

When receiving a VoIP push notification, the app should:

Perform the SIP login if not already logged in.
Process any incoming invites and establish the call.
Actual Behavior:

The app receives the VoIP push notification and shows the CallKit UI.
SIP login does not occur, and no invite is received, preventing call establishment.
Additional Information:

iOS Version: 16.3.1
Xcode Version: 15.4

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

1 participant