linphone-iphone
linphone-iphone copied to clipboard
Handling SIP Calls and VoIP Notifications in React Native
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