
Partager:
Abdul est défenseur des développeurs chez Vonage. Il a travaillé dans le domaine des produits de consommation en tant qu'ingénieur iOS. Pendant son temps libre, il aime faire du vélo, écouter de la musique et conseiller ceux qui commencent leur parcours dans la technologie.
Comment gérer les notifications push de VoIP avec iOS Callkit
Temps de lecture : 8 minutes
Dans ce tutoriel, vous utiliserez CallKit pour gérer les notifications push VoIP envoyées à un appareil iOS lors de l'utilisation du Client Vonage Client SDK pour iOS. Les notifications push VoIP constituent la principale méthode de réception des appels entrants avec le Client SDK puisqu'elles atteindront l'appareil, que votre application soit au premier plan ou non. CallKit vous permet d'intégrer votre application iOS au système afin que votre application puisse ressembler à un appel téléphonique iOS natif. L'utilisation du Client SDK de Vonage avec Callkit vous permettra d'intégrer les appels dans votre application tout en ayant une interface cohérente et familière pour les appels entrants.
Conditions préalables
Un Account API Vonage. Si vous n'en avez pas encore, vous pouvez vous inscrire aujourd'hui.
Un compte Apple Developer et un appareil iOS (ou un simulateur dans Xcode 14 et plus).
Un Account GitHub.
Xcode 12 et Swift 5 ou supérieur.
Cocoapods pour installer le Vonage Client SDK pour iOS.
Notre interface de ligne de commande. Vous pouvez l'installer avec
npm install -g @vonage/cli.
Le projet Starter
Ce blog s'appuiera sur l'article "Recevoir un appel téléphonique dans l'application". "Recevoir un appel téléphonique in-app" du portail des développeurs de Vonage. Ce tutoriel commencera à partir de l'état fini du projet du tutoriel. Soit vous suivez, soit si vous êtes déjà familier avec la construction d'une application vocale Vonage Client SDK, vous pouvez cloner le projet de démarrage depuis GitHub.
Mise en place de certificats de poussée
Il existe deux types de notifications push que vous pouvez utiliser dans une application iOS, les push VoIP avec PushKit ou les notifications utilisateur. Ce tutoriel se concentrera sur les notifications VoIP. Le service Apple Push Notifications (APNs) utilise l'authentification par certificat pour sécuriser les connexions entre les APNs et les serveurs Vonage. Vous devrez donc créer un certificat et le télécharger vers les serveurs Vonage afin que Vonage puisse envoyer un push à l'appareil en cas d'appel entrant.
Ajout d'une capacité de notification push
Pour utiliser les notifications push, vous devez ajouter la capacité de notification push à votre projet Xcode. Assurez-vous d'être connecté à votre Account de développeur Apple dans Xcode via les préférences. Si c'est le cas, sélectionnez votre cible, puis choisissez Signing & Capabilities:

Sélectionnez ensuite "Ajouter une capacité" et ajoutez le champ "Notifications push". Notifications push et Modes d'arrière-plan :

Sous la rubrique Modes d'arrière-plan, sélectionnez Voice over IP et Traitement en arrière-plan. Si Xcode gère automatiquement la signature de votre application, il mettra à jour le profil de provisionnement lié à votre identifiant d'ensemble pour inclure les capacités.
Lorsque vous utilisez des notifications push VoIP, vous devez utiliser le framework CallKit. Liez-le à votre projet en l'ajoutant sous Frameworks, Libraries, and Embedded Content (Cadres, bibliothèques et contenu intégré) sous Général :

Générer un certificat Push
Pour générer un certificat push, vous devez vous connecter à votre Account de développeur Apple et vous rendre dans la section Certificats, identifiants et profils et ajouter un nouveau certificat :

Choisir Apple Push Notification service SSL (Sandbox & Production) et continuez.

Vous devez maintenant choisir l'App ID de l'application à laquelle vous souhaitez ajouter des notifications VoIP push et continuer. Si votre application n'est pas listée, vous devrez créer un App ID. Xcode peut le faire pour vous s'il gère automatiquement votre signature. Dans le cas contraire, vous pouvez créer un nouvel identifiant d'application dans la section Certificats, identifiants et profils sous Identifiants. Veillez à sélectionner la capacité de notifications push lors de cette opération.
Vous serez invité à télécharger une demande de signature de certificat (CSR). Vous pouvez suivre les instructions sur site d'aide d'Apple pour créer une CSR sur votre Mac. Une fois la CSR téléchargée, vous pourrez télécharger le certificat. Double-cliquez sur le fichier .cer pour l'installer dans le trousseau d'accès.
Pour obtenir le certificat push dans le format requis par les serveurs Vonage, vous devez l'exporter. Localisez votre certificat VoIP Services dans Keychain Access et cliquez avec le bouton droit de la souris pour l'exporter. Nommez l'exportation applecert et sélectionnez .p12 comme format :

Téléchargez votre certificat Push
Vous téléchargez votre certificat à Vonage à l'aide du le tableau de bord de l'API. Ouvrez votre application sur le tableau de bordpuis ouvrez la fenêtre "Enable notifications notifications" :
Push Upload on the dashboard
Vous pouvez télécharger votre certificat .p12 de l'étape précédente et ajouter un mot de passe si nécessaire.
La classe ClientManager
Créez un nouveau fichier Swift (CMD + N) et l'appeler ClientManager. Cette classe encapsulera le code nécessaire à l'interface avec le Client SDK puisque vous aurez besoin d'obtenir des informations du Client SDK à plusieurs endroits dans les étapes suivantes :
Remplacez ALICE_JWT par le JWT que vous avez généré précédemment. Dans un environnement de production, c'est ici que vous récupérerez un JWT de votre serveur/terminal d'authentification.
Avec cette nouvelle classe, vous devrez déplacer le code du Client SDK de la classe ViewController vers la classe ClientManager dans la classe Les deux classes communiqueront avec les ClientManagerDelegate observateurs. Apportez les modifications suivantes à votre classe ViewController classe :
class ViewController: UIViewController {
private let connectionStatusLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
ClientManager.shared.delegate = self
connectionStatusLabel.text = ""
connectionStatusLabel.textAlignment = .center
connectionStatusLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(connectionStatusLabel)
view.addConstraints([
connectionStatusLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
connectionStatusLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
}
extension ViewController: ClientManagerDelegate {
func clientStatusUpdated(_ clientManager: ClientManager, status: String) {
DispatchQueue.main.async {
self.connectionStatusLabel.text = status
}
}
} S'inscrire aux notifications push
L'étape suivante consiste à enregistrer un appareil pour les notifications push afin de permettre à Vonage de savoir à quel appareil envoyer la notification push pour quel utilisateur. Dans la classe ClientManager ajoutez la propriété pushToken et les fonctions suivantes pour gérer le jeton push de l'appareil :
final class ClientManager: NSObject {
public var pushToken: Data?
weak var delegate: ClientManagerDelegate?
...
func invalidatePushToken(_ completion: (() -> Void)? = nil) {
print("VPush: Invalidate token")
if let deviceId = UserDefaults.standard.object(forKey: Constants.deviceId) as? String {
client.unregisterDeviceTokens(byDeviceId: deviceId) { error in
if error == nil {
self.pushToken = nil
UserDefaults.standard.removeObject(forKey: Constants.pushToken)
UserDefaults.standard.removeObject(forKey: Constants.deviceId)
completion?()
}
}
} else {
completion?()
}
}
private func registerPushIfNeeded(with token: Data) {
shouldRegisterToken(with: token) { shouldRegister in
if shouldRegister {
self.client.registerVoipToken(token, isSandbox: true) { error, deviceId in
if error == nil {
print("VPush: push token registered")
UserDefaults.standard.setValue(token, forKey: Constants.pushToken)
UserDefaults.standard.setValue(deviceId, forKey: Constants.deviceId)
} else {
print("VPush: registration error: \(String(describing: error))")
return
}
}
}
}
}
private func shouldRegisterToken(with token: Data, completion: @escaping (Bool) -> Void) {
let storedToken = UserDefaults.standard.object(forKey: Constants.pushToken) as? Data
if let storedToken = storedToken, storedToken == token {
completion(false)
return
}
invalidatePushToken {
completion(true)
}
}La fonction registerPushIfNeeded prend un jeton et utilise ensuite la fonction shouldRegisterToken pour vérifier si le jeton a déjà été enregistré. Si ce n'est pas le cas, registerVoipToken sur le client enregistrera la notification push avec Vonage. Dans la classe AppDelegate vous pouvez maintenant vous enregistrer pour les notifications push VoIP. Importer PushKit en haut du fichier :
import PushKitAjouter une instance locale de la ClientManager classe :
class AppDelegate: UIResponder, UIApplicationDelegate {
...
private let clientManager = ClientManager.shared
...
}Créez une nouvelle extension à la fin du fichier, qui contient une fonction permettant d'enregistrer l'appareil pour les notifications push :
extension AppDelegate: PKPushRegistryDelegate {
func registerForVoIPPushes() {
let voipRegistry = PKPushRegistry(queue: nil)
voipRegistry.delegate = self
voipRegistry.desiredPushTypes = [PKPushType.voIP]
}
}Mettre à jour la fonction didFinishLaunchingWithOptions pour appeler la fonction registerForVoIPPushes et se connecter au Client SDK :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
AVAudioSession.sharedInstance().requestRecordPermission { (granted:Bool) in
print("Allow microphone use. Response: \(granted)")
}
registerForVoIPPushes()
clientManager.login()
return true
}Ajouter les PKPushRegistryDelegate pour gérer l'enregistrement des notifications push dans l'extension :
extension AppDelegate: PKPushRegistryDelegate {
...
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
clientManager.pushToken = pushCredentials.token
}
func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
clientManager.invalidatePushToken(nil)
}
}Le jeton de poussée est stocké en tant que propriété de la classe ClientManager car vous ne voulez enregistrer le jeton avec Vonage que lorsque le client est connecté. login dans la classe ClientManager pour gérer cela :
func login(isPushLogin: Bool = false) {
print("VPush: Login - isPush:", isPushLogin)
guard !isActiveCall else { return }
ongoingPushLogin = isPushLogin
getJWT { jwt in
self.client.createSession(jwt) { error, sessionID in
let statusText: String
if error == nil {
statusText = "Connected"
if isPushLogin {
self.handlePushLogin()
} else {
self.handleLogin()
}
} else {
statusText = error!.localizedDescription
}
self.delegate?.clientStatusUpdated(self, status: statusText)
}
}
}
private func handlePushLogin() {
ongoingPushLogin = false
if let storedAction = storedAction {
storedAction()
}
}
private func handleLogin() {
if let token = pushToken {
registerPushIfNeeded(with: token)
}
} Gérer les notifications push entrantes
Une fois l'appareil enregistré, il peut maintenant recevoir des notifications push de Vonage. Le Client SDK dispose de fonctions permettant de vérifier si la charge utile d'une notification push est celle attendue et de traiter la charge utile. Lorsque la fonction processCallInvitePushData est appelée, elle convertit la charge utile en un appel qui est reçu sur la fonction didReceiveInviteForCall de la fonction VGVoiceClientDelegate.
Comme pour l'enregistrement d'un jeton de poussée, vous ne voulez traiter une poussée entrante que lorsque le Client SDK a été connecté. Mettez en œuvre les fonctions de la classe ClientManager avec une variable locale pour stocker un push entrant :
final class ClientManager: NSObject {
...
private var ongoingPushLogin = false
private var ongoingPushKitCompletion: () -> Void = { }
private var storedAction: (() -> Void)?
...
func isVonagePush(with userInfo: [AnyHashable : Any]) -> Bool {
VGVoiceClient.vonagePushType(userInfo) == .unknown ? false : true
}
func processPushPayload(with payload: [AnyHashable : Any], pushKitCompletion: @escaping () -> Void) -> String? {
self.ongoingPushKitCompletion = pushKitCompletion
return client.processCallInvitePushData(payload)
}
...
}Le PKPushRegistryDelegate possède une fonction qui est appelée lorsqu'il y a un "push" entrant appelé didReceiveIncomingPushWith l'ajouter à l'extension PKPushRegistryDelegate dans le AppDelegate.swift fichier :
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
if clientManager.isVonagePush(with: payload.dictionaryPayload) {
clientManager.login(isPushLogin: true)
_ = clientManager.processPushPayload(with: payload.dictionaryPayload, pushKitCompletion: completion)
}
}Il est recommandé d'effectuer une connexion lorsque vous recevez une notification push VoIP entrante, c'est pourquoi login est appelé ici. Ceci utilise la logique de la classe ClientManager qui stocke les informations relatives à la notification push à utiliser une fois la connexion terminée. La logique sera mise en œuvre ultérieurement.
Lorsque votre application iOS reçoit une notification push VoIP entrante, vous devez la gérer à l'aide de la classe CXProvider du framework CallKit. Créez un nouveau fichier Swift (CMD + N) appelé ProviderDelegate:
import CallKit
import AVFoundation
import VonageClientSDKVoice
final class ProviderDelegate: NSObject {
private let provider: CXProvider
private let callController = CXCallController()
private var activeCall: UUID? = nil
override init() {
provider = CXProvider(configuration: ProviderDelegate.providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
static var providerConfiguration: CXProviderConfiguration = {
let providerConfiguration = CXProviderConfiguration()
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.generic, .phoneNumber]
return providerConfiguration
}()
}Le bien callController est un objet CXCallController utilisé par la classe pour gérer les actions de l'utilisateur sur l'interface CallKit. Ensuite, créez une extension à la fin du fichier pour implémenter la propriété CXProviderDelegate:
extension ProviderDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
activeCall = nil
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
ClientManager.shared.answer(activeCall!.uuidString.lowercased()) { error in
if error == nil {
action.fulfill()
} else {
action.fail()
}
}
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
hangup(action: action)
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
VGVoiceClient.enableAudio(audioSession)
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
VGVoiceClient.disableAudio(audioSession)
}
}Lorsque CallKit active et désactive la session audio, les fonctions déléguées activent et désactivent l'audio du Client SDK à l'aide de la session audio CallKit.
Lorsque l'interface utilisateur CallKit répond à l'appel, elle appelle la fonction CXAnswerCallAction fonction déléguée. Celle-ci appelle la fonction answer que vous mettrez en œuvre sur la fonction ClientManager dans une étape ultérieure.
CXEndCallAction est appelée lorsque l'appel est terminé depuis l'interface utilisateur CallKit, qui appelle la fonction hangup que vous implémenterez ensuite. Implémentez le reste des fonctions nécessaires dans la ProviderDelegate classe :
final class ProviderDelegate: NSObject {
...
func reportCall(_ callID: String, caller: String, completion: @escaping () -> Void) {
activeCall = UUID(uuidString: callID)
let update = CXCallUpdate()
update.localizedCallerName = caller
provider.reportNewIncomingCall(with: activeCall!, update: update) { error in
if error == nil {
completion()
}
}
}
func didReceiveHangup(_ callID: String) {
let uuid = UUID(uuidString: callID)!
provider.reportCall(with: uuid, endedAt: Date.now, reason: .remoteEnded)
}
func reportFailedCall(_ callID: String) {
let uuid = UUID(uuidString: callID)!
provider.reportCall(with: uuid, endedAt: Date.now, reason: .failed)
}
private func hangup(action: CXEndCallAction) {
if activeCall == nil {
endCallTransaction(action: action)
} else {
ClientManager.shared.reject(activeCall!.uuidString.lowercased()) { error in
if error == nil {
self.endCallTransaction(action: action)
}
}
}
}
private func endCallTransaction(action: CXEndCallAction) {
self.callController.request(CXTransaction(action: action)) { error in
if error == nil {
self.activeCall = nil
action.fulfill()
} else {
action.fail()
}
}
}
}La fonction reportCall fonction calls reportNewIncomingCall qui déclenche l'interface utilisateur du système CallKit, les autres fonctions permettent de mettre à jour ou de terminer les appels. Maintenant que la ProviderDelegate est complète, vous pouvez mettre à jour la classe ClientManager pour l'utiliser. Ajoutez la propriété providerDelegate au gestionnaire de clients :
final class ClientManager: NSObject {
...
private let providerDelegate = ProviderDelegate()
...
}Ensuite, il faut mettre en œuvre l'option VGVoiceClientDelegate:
extension ClientManager: VGVoiceClientDelegate {
func voiceClient(_ client: VGVoiceClient, didReceiveInviteForCall callId: VGCallId, from caller: String, with type: VGVoiceChannelType) {
print("VPush: Received invite", callId)
providerDelegate.reportCall(callId, caller: caller, completion: ongoingPushKitCompletion)
}
func voiceClient(_ client: VGVoiceClient, didReceiveHangupForCall callId: VGCallId, withQuality callQuality: VGRTCQuality, reason: VGHangupReason) {
print("VPush: Received hangup")
isActiveCall = false
providerDelegate.didReceiveHangup(callId)
}
func voiceClient(_ client: VGVoiceClient, didReceiveInviteCancelForCall callId: String, with reason: VGVoiceInviteCancelReason) {
print("VPush: Received invite cancel")
providerDelegate.reportFailedCall(callId)
}
func client(_ client: VGBaseClient, didReceiveSessionErrorWith reason: VGSessionErrorReason) {
let reasonString: String!
switch reason {
case .tokenExpired:
reasonString = "Expired Token"
case .pingTimeout, .transportClosed:
reasonString = "Network Error"
default:
reasonString = "Unknown"
}
delegate?.clientStatusUpdated(self, status: reasonString)
}
}Une fois que le SDK a traité l'appel, vous recevrez une invitation à l'appel sur la fonction déléguée didReceiveInviteForCall qui, à son tour, signalera l'appel. didReceiveHangupForCall et didReceiveInviteCancelForCall appellent également leurs fonctions respectives sur le délégué du fournisseur. Pour compléter la classe ClientManager ajoutez les éléments answer et reject pour compléter la classe :
func answer(_ callID: String, completion: @escaping (Error?) -> Void) {
let answerAction = {
print("VPush: Answer", callID)
self.isActiveCall = true
self.client.answer(callID, callback: completion)
}
if ongoingPushLogin {
print("VPush: Storing answer")
storedAction = answerAction
} else {
answerAction()
}
}
func reject(_ callID: String, completion: @escaping (Error?) -> Void) {
let rejectAction = {
print("VPush: Reject", callID)
self.isActiveCall = false
self.client.reject(callID, callback: completion)
}
if ongoingPushLogin {
print("VPush: Storing Reject")
storedAction = rejectAction
} else {
rejectAction()
}
}Là encore, vous ne pouvez répondre à un appel ou le rejeter qu'après avoir ouvert une session dans le Client SDK. Les deux fonctions utilisent l'élément ongoingPushLogin pour vérifier si la connexion a été effectuée avec succès ; si ce n'est pas le cas, l'action est enregistrée à l'aide de la fonction storedAction. Si vous examinez la fonction handlePushLogin vous pouvez voir que lorsque la connexion est terminée, elle appelle une action stockée s'il y en a une.
Essayez-le
Construisez et exécutez (CMD + R) le projet sur votre appareil iOS (ou simulateur dans Xcode 14 et supérieur), acceptez les permissions du microphone et verrouillez l'appareil. Appelez ensuite le numéro lié à votre Applications Vonage de tout à l'heure. Vous verrez l'appel entrant directement sur votre écran de verrouillage ; puis, lorsque vous décrocherez, l'écran d'appel familier d'iOS s'affichera :


Si vous consultez le journal des appels sur l'appareil, vous verrez également l'appel répertorié.
Quelle est la prochaine étape ?
Vous pouvez trouver le projet terminé sur GitHub. Vous pouvez faire beaucoup plus avec le Client SDK et CallKit ; vous pouvez utiliser CallKit pour les appels sortants. Pour en savoir plus sur le Client SDK, consultez la page Aperçu du Client SDK de Vonage et CallKit sur developer.apple.com.
Partager:
Abdul est défenseur des développeurs chez Vonage. Il a travaillé dans le domaine des produits de consommation en tant qu'ingénieur iOS. Pendant son temps libre, il aime faire du vélo, écouter de la musique et conseiller ceux qui commencent leur parcours dans la technologie.