|
import ExpoModulesCore |
|
import Foundation |
|
import PushKit |
|
import CallKit |
|
|
|
class VoipPushDelegate: NSObject, PKPushRegistryDelegate, CXProviderDelegate { |
|
|
|
var onTokenReceived: ((String) -> Void)? |
|
var onIncomingPush: ((PKPushPayload) -> Void)? |
|
var onCallAccepted: ((UUID) -> Void)? |
|
var onCallEnded: ((UUID) -> Void)? |
|
|
|
var voipRegistrationToken: String? |
|
var pushRegistry: PKPushRegistry? |
|
|
|
private let callProvider: CXProvider |
|
private let callController = CXCallController() |
|
|
|
override init() { |
|
let config = CXProviderConfiguration(localizedName: "App Name") |
|
config.supportsVideo = true |
|
config.maximumCallsPerCallGroup = 1 |
|
config.supportedHandleTypes = [.generic] |
|
|
|
pushRegistry = PKPushRegistry(queue: DispatchQueue.main) |
|
|
|
callProvider = CXProvider(configuration: config) |
|
|
|
super.init() |
|
|
|
pushRegistry?.delegate = self |
|
pushRegistry?.desiredPushTypes = [.voIP] |
|
|
|
|
|
callProvider.setDelegate(self, queue: nil) |
|
|
|
} |
|
|
|
func pushRegistry( |
|
_ registry: PKPushRegistry, |
|
didUpdate pushCredentials: PKPushCredentials, |
|
for type: PKPushType |
|
) { |
|
guard type == .voIP else { return } |
|
|
|
let tokenData = pushCredentials.token |
|
let tokenParts = tokenData.map { String(format: "%02x", $0) } |
|
voipRegistrationToken = tokenParts.joined() |
|
|
|
onTokenReceived?(voipRegistrationToken ?? "") |
|
} |
|
|
|
func pushRegistry( |
|
_ registry: PKPushRegistry, |
|
didReceiveIncomingPushWith payload: PKPushPayload, |
|
for type: PKPushType, |
|
completion: @escaping () -> Void |
|
) { |
|
guard type == .voIP else { |
|
completion() |
|
return |
|
} |
|
|
|
// CallKit expects us to call this immediately |
|
let update = CXCallUpdate() |
|
update.hasVideo = true |
|
update.remoteHandle = CXHandle( |
|
type: .generic, |
|
value: (payload.dictionaryPayload["callerName"] as? String ?? "Unknown Caller") |
|
) |
|
|
|
let uuid = payload.dictionaryPayload["uuid"] as? String |
|
|
|
callProvider.reportNewIncomingCall(with: UUID(uuidString: uuid ?? "") ?? UUID(), update: update) { error in |
|
if let error = error { |
|
print("Error reporting call: \(error)") |
|
} else { |
|
self.onIncomingPush?(payload) |
|
} |
|
completion() |
|
} |
|
} |
|
|
|
func startCall(callUUID: String, handle: String, callerName: String) { |
|
let callUUIDObj = UUID(uuidString: callUUID) ?? UUID() |
|
let handle = CXHandle(type: .generic, value: handle) |
|
|
|
let callAction = CXStartCallAction(call: callUUIDObj, handle: handle) |
|
callAction.isVideo = true |
|
|
|
let transaction = CXTransaction(action: callAction) |
|
self.callController.request(transaction, completion: { error in}) |
|
} |
|
|
|
func endCall(callUUID: String) { |
|
let callUUIDObj = UUID(uuidString: callUUID) ?? UUID() |
|
let action = CXEndCallAction(call: callUUIDObj) |
|
|
|
let transaction = CXTransaction(action: action) |
|
self.callController.request(transaction, completion: { error in}) |
|
} |
|
|
|
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { |
|
// Handle the action, e.g., start audio, setup media, etc. |
|
let callUUID = action.callUUID |
|
self.onCallAccepted?(callUUID) |
|
action.fulfill() |
|
} |
|
|
|
func provider(_ provider: CXProvider, perform action: CXEndCallAction) { |
|
let callUUID = action.callUUID |
|
self.onCallEnded?(callUUID) |
|
action.fulfill() |
|
} |
|
|
|
func providerDidReset(_ provider: CXProvider) { |
|
// Handle provider reset if needed |
|
} |
|
} |
|
|
|
public final class ExpoVoipPushTokenModule: Module { |
|
private let delegate = VoipPushDelegate() |
|
|
|
private func initializePushDelegate() { |
|
|
|
delegate.onTokenReceived = { [weak self] token in |
|
self?.sendEvent("onRegistration", ["voipToken": token]) |
|
} |
|
|
|
delegate.onIncomingPush = { [weak self] payload in |
|
let payloadDict = payload.dictionaryPayload |
|
|
|
self?.sendEvent("notification", ["payload": payloadDict]) |
|
} |
|
|
|
delegate.onCallAccepted = { [weak self] uuid in |
|
self?.sendEvent("onCallAccepted", ["callUUID": uuid.uuidString]) |
|
} |
|
|
|
delegate.onCallEnded = { [weak self] uuid in |
|
self?.sendEvent("onCallEnded", ["callUUID": uuid.uuidString]) |
|
} |
|
} |
|
|
|
private func startCall(callUUID: String, handle: String, callerName: String) { |
|
delegate.startCall(callUUID: callUUID, handle: handle, callerName: callerName) |
|
} |
|
|
|
private func endCall(callUUID: String) { |
|
delegate.endCall(callUUID: callUUID) |
|
} |
|
|
|
public func definition() -> ModuleDefinition { |
|
|
|
Name("ExpoVoipPushToken") |
|
|
|
Events("onRegistration", "notification", "onCallAccepted", "onCallEnded") |
|
|
|
OnCreate(){ |
|
self.initializePushDelegate() |
|
} |
|
|
|
Function("startCall") { (callUUID: String, handle: String, callerName: String) -> Void in |
|
self.startCall(callUUID: callUUID, handle: handle, callerName: callerName) |
|
} |
|
|
|
Function("endCall") { (callUUID: String) -> Void in |
|
self.endCall(callUUID: callUUID) |
|
} |
|
|
|
Function("registerVoipPushToken") { |
|
self.sendEvent("onRegistration", ["voipToken": self.delegate.voipRegistrationToken ?? ""]) |
|
} |
|
} |
|
} |
thank you!