SDK mobile headless pour iOS : premiers pas

Ce document explique comment commencer à utiliser le SDK Headless Mobile pour iOS.

Le SDK Headless Mobile pour iOS vous permet d'ajouter une assistance optimisée par la plate-forme CCAI directement dans votre propre expérience iOS intégrée, sans utiliser d'UI de plate-forme CCAI prédéfinie.

Au lieu de présenter un widget portant la marque CCAI Platform, vous :

  • Intégrez le SDK CCAI iOS à votre application.

  • Créez vos propres écrans, votre propre navigation et votre propre conception visuelle.

  • Utilisez le SDK pour :

    • démarrer et gérer des sessions de chat et vocales ;

    • Proposez des options d'e-mail, d'appels planifiés et de déviation.

    • Prise en charge des actions intelligentes, des pièces jointes et des flux post-session (CSAT, enquêtes, agent virtuel).

CCAI Platform gère la logique du centre de contact (routage, files d'attente, canaux, configuration, rapports). Vous êtes propriétaire de l'intégralité de l'expérience dans l'application (UI, flux et branding).

Si vous préférez une UI configurable prédéfinie au lieu de créer la vôtre, utilisez le SDK mobile standard.

Conditions requises

Voici les exigences à respecter pour intégrer le SDK mobile sans interface utilisateur pour iOS.

Exigences liées à la plate-forme iOS

  • Version minimale d'iOS : iOS 16.6 ou version ultérieure

  • Langue : Swift

  • IDE : dernière version stable de Xcode compatible avec votre application

  • Architectures : architectures iOS standards compatibles avec votre chaîne d'outils Xcode actuelle.

Autorisations d'applications

Étant donné que votre application intégrée gère l'UI pour les actions intelligentes (prise de photos, enregistrement de vidéos ou validation de l'identité), vous devez déclarer les autorisations de confidentialité obligatoires d'Apple.

Ajoutez les clés suivantes à votre fichier Info.plist en décrivant comment votre application utilise les données :

  • NSCameraUsageDescription : obligatoire pour que les utilisateurs puissent prendre et envoyer des photos/vidéos, ou pour les actions intelligentes demandées par l'agent.

  • NSMicrophoneUsageDescription : obligatoire pour les appels vocaux instantanés et pour enregistrer des vidéos avec du son.

  • NSPhotoLibraryUsageDescription : obligatoire pour que les utilisateurs puissent importer des photos ou des vidéos existantes depuis la pellicule.

  • NSFaceIDUsageDescription : obligatoire si vous acceptez les actions intelligentes de validation biométrique.

Exigences concernant les instances CCAI Platform

Pour obtenir les informations suivantes, vous devez avoir accès aux paramètres pour les développeurs de votre instance CCAI Platform :

  • Clé d'entreprise

  • Code secret de l'entreprise

  • URL de l'hôte : nom d'hôte de la plate-forme CCAI (par exemple, your_subdomain.ccaiplatform.com)

Ces valeurs sont utilisées pour :

  • Clé d'entreprise + URL de l'hôte : dans votre application iOS pour initialiser le SDK.

  • Code secret de l'entreprise : dans votre backend pour signer les jetons Web JSON (JWT) utilisés pour authentifier les utilisateurs.

Notifications push

Pour les appels et certaines actions intelligentes, vous avez besoin des éléments suivants :

  • Configuration du service Apple Push Notification (APNs) pour les notifications push standards.

  • Un certificat de services VoIP pour prendre en charge les appels entrants et les notifications spécifiques liées aux appels.

Vous découvrirez comment :

  • Configurez les identifiants APNs et VoIP dans le portail des développeurs Apple.

  • Importez les certificats convertis dans le portail administrateur de la plate-forme CCAI sous Paramètres > Paramètres pour les développeurs > Application mobile.

  • Enregistrez les notifications à distance et les notifications push VoIP dans votre application.

  • Transmettez les jetons d'appareil appropriés au SDK.

  • Dans Target > Signing & capabilities (Cible > Signature et capacités), activez les éléments suivants :

    • Fonctionnalité Notifications push.

    • La fonctionnalité Modes d'arrière-plan avec les éléments suivants cochés :

      • Voix sur IP : obligatoire pour les notifications push VoIP (appels entrants, actions intelligentes utilisant le réseau téléphonique public commuté).

      • Audio, AirPlay et Picture-in-picture : requis pour que l'audio des appels vocaux continue lorsque l'application est en arrière-plan.

      • Notifications à distance : requises pour la diffusion push APNs standard.

Comment le SDK iOS sans interface graphique s'intègre à votre application

Le SDK Headless Mobile pour iOS peut être considéré comme composé de trois couches :

Plate-forme et SDK CCAI Platform

Le SDK interagit de manière sécurisée avec la plate-forme CCAI Platform et fournit les éléments suivants :

  • Authentification à l'aide de jetons Web JSON (JWT).

  • Métadonnées de routage des files d'attente et des canaux (chat, voix, appel planifié, e-mail, liens de redirection externe).

  • Gestion de l'état de la session en temps réel.

  • Pipeline de transport sécurisé pour les actions intelligentes, les pièces jointes et le partage d'écran.

  • Logique de redirection et évaluation hiérarchique post-session.

Votre application iOS (vous gérez l'UI et la logique)

Vous contrôlez l'ensemble de l'expérience visuelle et de navigation :

  • Points d'entrée : par exemple, un onglet Aide ou un bouton Nous contacter.

  • Menus : façon dont vous affichez les files d'attente (par exemple, Facturation, Assistance technique).

  • Interface utilisateur en session : vos bulles de chat, écrans d'appel et commandes personnalisés.

  • Fonctionnalités de votre application : elle appelle les API du SDK pour démarrer et mettre fin aux sessions, écoute les événements du SDK et gère la navigation du contrôleur de vue.

Configuration du portail administrateur CCAI Platform

Vos administrateurs CCAI Platform configurent les règles d'engagement (heures d'ouverture, canaux disponibles, seuils de temps d'attente et enquêtes). Le SDK lit cette configuration et l'expose à votre application sous forme de données et d'événements bruts. Vous décidez comment dessiner cette configuration à l'écran.

Récupérer les identifiants de l'entreprise

Avant d'intégrer le SDK, obtenez les identifiants à partir de votre instance CCAI Platform :

  1. Connectez-vous au portail administrateur de la plate-forme CCAI avec un compte administrateur.

  2. Accédez à Paramètres > Paramètres pour les développeurs.

  3. Sous Clé et code secret de l'entreprise, copiez :

    • Clé d'entreprise

    • Code secret de l'entreprise

  4. Notez l'URL de votre hôte : nom d'hôte de la plate-forme CCAI (par exemple, your_subdomain.ccaiplatform.com).

Vous utiliserez ces produits :

  • Dans votre application iOS : clé de l'entreprise + URL de l'hôte.

  • Sur votre serveur backend : code secret de l'entreprise pour signer les jetons JWT :

    • Authentification de l'utilisateur final.

    • Données ou contexte personnalisés facultatifs utilisés par CCAI Platform.

Ajouter le SDK à votre application iOS

Le SDK sans interface graphique est conçu à l'aide d'une architecture modulaire. Vous installez le cœur CCAIKit en même temps que les modules de fonctionnalités spécifiques dont votre application a besoin (par exemple, CCAIChat ou CCAIScreenShare).

Pour intégrer le SDK iOS à votre application, suivez la procédure ci-dessous.

  1. Ajoutez le code ci-dessous à votre fichier Package.swift :

    dependencies: [
       .package(url: "https://github.com/UJET/ccai-ios-sdk.git", from: "3.3.1")
    ],
    targets: [
       .target(
           name: "YourTargetName",
           dependencies: [
               .product(name: "CCAIKit", package: "CCAIKit")
           ]
       )
    ]
    
  2. Importez CCAIKit dans votre fichier AppDelegate :

    import CCAIKit
    
  3. Si votre projet est SwiftUI, déclarez @UIApplicationDelegateAdaptor dans votre structure App :

    @main
    struct YourAppName: App {
       @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
       // ...
    }
    

Initialiser le SDK sans interface graphique

Initialisez le SDK au lancement de l'application. En raison de l'architecture modulaire du SDK, vous devez d'abord initialiser le système CCAI de base, puis enregistrer les modules de service spécifiques (tels que le chat ou le partage d'écran).

Initialisation de base

Le SDK utilise une structure InitOptions qui regroupe toute la configuration requise :

import CCAIKit
import CCAIChat       // If using Chat
import CCAIScreenShare // If using Screen Share

class AppDelegate: NSObject, UIApplicationDelegate, CCAIDelegate {

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

        // 1. Configure initialization options
        let options = InitOptions(
            key: "YOUR_COMPANY_KEY",
            urlHost: "your_subdomain.ccaiplatform.com",
            delegate: self,
            languageCode: "en",       // ISO 639 code (defaults to "en")
            cacheAuthToken: true       // Whether to cache the auth token (defaults to true)
        )

        // 2. Initialize the core SDK
        do {
            try CCAI.shared.initialize(options: options)
        } catch {
            print("Failed to initialize CCAI: \(error.localizedDescription)")
            return true
        }

        // 3. Initialize feature modules
        // Chat minimal (no options required)
        CCAI.shared.initializeChat()

        // Chat with optional configuration
        // let chatOptions = ChatOptions(
        //     delegate: webFormDelegate,           // Implement to intercept and render custom web forms within your own UI
        //     downloadTranscriptVisibility: .hideInPostChat, // Hide the post-chat download affordance
        //     greeting: "Hello! How can I help you?"
        // )
        // CCAI.shared.initializeChat(chatOptions)

        // Screen Share (optional)
        // let screenShareOptions = ScreenShareOptions(
        //     key: "YOUR_SCREEN_SHARE_KEY",
        //     domain: "your_domain.com"
        // )
        // CCAI.shared.initializeScreenShare(screenShareOptions)

        return true
    }

    // ... CCAIDelegate methods (Authentication) go here ...
}

Documentation de référence sur la configuration de InitOptions

public struct InitOptions {
    /// The company key used for authentication
    public let key: String

    /// The host URL for CCAI platform (for example, "my-unique-instance.uc1.ccaiplatform.com")
    public let urlHost: String

    /// The preferred language code for localization (defaults to "en")
    public var languageCode: String

    /// The delegate that receives SDK events and callbacks
    public weak var delegate: CCAIDelegate?

    /// Whether to cache the authentication token (defaults to true)
    public let cacheAuthToken: Bool
}

Services SDK disponibles

Après l'initialisation, vous pouvez accéder à différents services via le singleton CCAI.shared :

let authService = CCAI.shared.authService
let companyService = CCAI.shared.companyService
let queueMenuService = CCAI.shared.queueMenuService
let optionsService = CCAI.shared.optionsService
let languageService = CCAI.shared.languageService
let pushNotificationService = CCAI.shared.pushNotificationService
let endUserService = CCAI.shared.endUserService
let chatService = CCAI.shared.chatService           // Only available after initializeChat()
let screenShareService = CCAI.shared.screenShareService // Only available after initializeScreenShare()
let rateService = CCAI.shared.rateService            // For CSAT ratings and survey submission
let smartActionService = CCAI.shared.smartActionService // Dedicated service for smart action lifecycle

Récupérer la configuration de l'entreprise

Après l'initialisation, récupérez la configuration au niveau de l'entreprise à l'aide de companyService. Cela permet de remplir les sélecteurs de langue, d'afficher le nom de l'entreprise ou de lire les coordonnées du service d'assistance avant que l'utilisateur ne soit placé dans une file d'attente.

do {
    let company = try await CCAI.shared.companyService?.get()
    print("Company: \(company?.displayName ?? "Unknown")")
} catch {
    print("Failed to get company info: \(error)")
}

Authentifier les utilisateurs finaux

Le SDK mobile sans interface graphique utilise des jetons Web JSON (JWT) pour authentifier les utilisateurs et transmettre de manière sécurisée des informations contextuelles au CRM de l'agent.

Fonctionnement

Le SDK utilise un flux d'authentification asynchrone simplifié en deux étapes :

  1. Le SDK détermine qu'il doit authentifier l'utilisateur.

  2. Il appelle votre méthode CCAIDelegate : ccaiShouldAuthenticate(), qui est une fonction asynchrone.

  3. Votre application signe un jeton JWT à distance sur votre serveur backend à l'aide du code secret de votre entreprise.

  4. Votre application transmet ensuite le JWT signé à CCAI.shared.authService?.authenticate(jwt) pour l'échanger contre un jeton d'authentification.

  5. Le jeton d'authentification est renvoyé au SDK pour finaliser la connexion.

Implémenter CCAIDelegate pour l'authentification

Votre application doit implémenter le protocole CCAIDelegate. Le SDK appelle la méthode déléguée asynchrone unique lorsqu'il a besoin d'une authentification.

Protocole CCAIDelegate

public protocol CCAIDelegate: AnyObject {
    /// Authenticates the end user and returns an auth token
    func ccaiShouldAuthenticate() async -> String?
}

Exemple d'implémentation (Swift)

class AppDelegate: CCAIDelegate {
    func ccaiShouldAuthenticate() async -> String? {
        // 1. Sign JWT remotely on your backend server
        guard let jwt = await signJWTRemotely() else { return nil }

        // 2. Authenticate JWT using authService to get an auth token
        return try? await CCAI.shared.authService?.authenticate(jwt)
    }
}

Exemple complet avec signature JWT pour référence et test

class AuthController: CCAIDelegate {
    func ccaiShouldAuthenticate() async -> String? {
        var jwt = JWT(claims: claims)

        // First, sign JWT with company secret (do this on your backend in production)
        guard let secret = self.companySecret,
              let key = secret.data(using: .utf8) else { return nil }
        let signer = JWTSigner.hs384(key: key)
        guard let encodedJWT = try? jwt.sign(using: signer) else { return nil }

        // Then authenticate JWT using authService to get an auth token
        return try? await CCAI.shared.authService?.authenticate(encodedJWT)
    }
}

Transmettre des données personnalisées au CRM

Si vous souhaitez transmettre des données contextuelles à l'agent (par exemple, l'OS de l'appareil actuel de l'utilisateur, sa position ou son niveau de compte), votre backend doit injecter un objet custom_data dans la charge utile JWT avant de la signer.

Chaque élément de données personnalisées doit être mis en forme en tant qu'objet JSON contenant un label (ce que voit l'agent), un value et un type.

Types de données acceptés

  • string : texte standard (par exemple, "iPhone 14 Pro").

  • number : entiers ou nombres à virgule flottante (par exemple, 1234 ou 99,99).

  • date : code temporel Unix UTC à 13 chiffres, y compris les millisecondes (par exemple, 1537399655992).

  • url : format d'URL HTTP/HTTPS standard.

  • boolean : valeur standard "true" ou "false".

Clés CRM réservées

La plate-forme CCAI prend en charge des clés réservées spécifiques qui déclenchent des comportements intégrés dans la plate-forme, comme le signalement d'un utilisateur en tant que VIP ou l'avertissement d'un agent concernant une personne malintentionnée. Elles doivent être mises en forme en tant que types booléens et ne sont acceptées que si la charge utile est signée à l'aide de la méthode JWT sécurisée.

  • reserved_verified_customer : indique si un client a été authentifié avec succès par vos systèmes internes.

  • reserved_bad_actor : signale à l'agent que l'utilisateur est potentiellement un spammeur ou un compte frauduleux.

  • reserved_repeat_customer : indique si ce client a fréquemment contacté l'assistance récemment.

Exemple de charge utile avec des clés réservées

{
  "iat": 1537399656,
  "exp": 1537400256,
  "custom_data": {
    "reserved_verified_customer": {
      "label": "Verified Customer",
      "value": true,
      "type": "boolean"
    },
    "reserved_bad_actor": {
      "label": "Bad Actor",
      "value": false,
      "type": "boolean"
    },
    "reserved_repeat_customer": {
      "label": "Repeat Customer",
      "value": false,
      "type": "boolean"
    }
  }
}

Gérer les jetons d'authentification

Le SDK fournit également des méthodes permettant de mettre à jour ou d'effacer manuellement le jeton d'authentification mis en cache :

// Set a new auth token
CCAI.shared.authService?.updateAuthToken("new_auth_token")

// Clear the current token (for example, on user logout)
CCAI.shared.authService?.updateAuthToken(nil)

Schéma de charge utile JWT des données personnalisées

Lorsque votre backend construit la charge utile finale à signer avec le code secret de votre entreprise, il doit suivre strictement ce schéma. Notez les codes temporels obligatoires iat (date d'émission) et exp (date d'expiration).

{
  "iat": 1537399656,
  "exp": 1537400256,
  "custom_data": {
    "os_version": {
      "label": "OS Version",
      "value": "16.4",
      "type": "string"
    },
    "membership_tier": {
      "label": "Membership Tier",
      "value": "Platinum",
      "type": "string"
    },
    "ssn_last_four": {
      "label": "SSN",
      "value": "1234",
      "type": "string",
      "invisible_to_agent": true
    },
    "reserved_verified_customer": {
      "label": "Verified Customer",
      "value": true,
      "type": "boolean"
    }
  }
}

Activer les notifications push

Le SDK utilise les notifications push pour :

  • Appels entrants.

  • Certaines actions intelligentes et certains événements liés aux appels.

  • Maintien de l'état lorsque l'application est en arrière-plan.

Configurer les certificats

CCAI Platform nécessite l'enregistrement de deux types de certificats push Apple différents dans le portail administrateur CCAI Platform :

  • Un certificat SSL Apple Push Notification Service (APNS) utilisé pour les notifications push à distance standards.

  • Certificat de services VoIP : utilisé pour les notifications d'appels entrants et certaines actions intelligentes liées aux appels.

Les deux certificats sont créés dans le portail Apple Developer, convertis au format PEM, puis importés dans le portail administrateur de la plate-forme CCAI.

Créer un certificat SSL APNs

Suivez les étapes suivantes, basées sur le guide officiel d'Apple Établir une connexion basée sur un certificat à APNs, pour créer un certificat SSL APNs.

  1. Connectez-vous à developer.apple.com et accédez à Certificates, Identifiers & Profiles (Certificats, identifiants et profils).

  2. Sélectionnez le bouton + pour créer un certificat.

  3. Sous "Services", sélectionnez Apple Push Notification service SSL (Sandbox & Production).

  4. Choisissez l'ID d'application qui correspond à l'identifiant du bundle de votre application, puis sélectionnez Continuer.

  5. Importez une demande de signature de certificat (CSR) générée à partir d'Accès au trousseau sur votre Mac :

    • Ouvrez Accès au trousseau d'accès > Assistant Certificat > Demander un certificat à une autorité de certification…

    • Saisissez votre adresse e-mail, laissez le champ "Adresse e-mail de l'autorité de certification" vide, sélectionnez Enregistré sur le disque, puis Continuer.

  6. Sélectionnez Continuer, puis Télécharger le fichier .cer généré.

  7. Double-cliquez sur le fichier .cer pour l'installer dans votre trousseau.

  8. Dans Trousseaux d'accès, recherchez le certificat installé, effectuez un clic droit dessus, puis sélectionnez Exporter… pour l'enregistrer au format .p12. Vous êtes invité à définir un mot de passe pour l'exportation.

  9. Convertissez le fichier .p12 au format PEM à l'aide de openssl :

    # Extract the certificate
    openssl pkcs12 -in apns_certificate.p12 -out apns_cert.pem -clcerts -nokeys
    
    # Extract the private key
    openssl pkcs12 -in apns_certificate.p12 -out apns_key.pem -nocerts -nodes
    
    # (Optional) Combine into a single PEM file
    cat apns_cert.pem apns_key.pem > apns_combined.pem
    

Créer un certificat de services VoIP

Les certificats de services VoIP permettent à votre application de recevoir des notifications push à priorité élevée via le framework PushKit d'Apple, qui peut réactiver votre application à partir d'un état terminé pour gérer les appels entrants. Pour connaître les dernières étapes officielles, consultez Créer des certificats de services VoIP dans la documentation Apple Developer.

  1. Connectez-vous à developer.apple.com et accédez à Certificates, Identifiers & Profiles (Certificats, identifiants et profils).

  2. Sélectionnez le bouton + pour créer un certificat.

  3. Sous "Services", sélectionnez Certificat de services VoIP.

  4. Choisissez l'ID d'application qui correspond à l'identifiant du bundle de votre application, puis sélectionnez Continuer.

  5. Importez la même requête de signature de certificat (CSR) que celle que vous avez générée pour votre certificat SSL APNs (ou créez-en une).

  6. Sélectionnez Continuer, puis Télécharger le fichier .cer généré.

  7. Double-cliquez sur le fichier .cer pour l'installer dans votre trousseau.

  8. Dans Trousseaux d'accès, recherchez le certificat des services VoIP installés, effectuez un clic droit dessus, puis sélectionnez Exporter… pour l'enregistrer au format .p12.

  9. Convertissez le fichier .p12 au format PEM à l'aide de openssl :

    # Extract the certificate
    openssl pkcs12 -in voip_certificate.p12 -out voip_cert.pem -clcerts -nokeys
    
    # Extract the private key
    openssl pkcs12 -in voip_certificate.p12 -out voip_key.pem -nocerts -nodes
    
    # (Optional) Combine into a single PEM file
    cat voip_cert.pem voip_key.pem > voip_combined.pem
    

Importer des certificats dans le portail administrateur CCAI Platform

  1. Connectez-vous au portail d'administration de la plate-forme CCAI.

  2. Accédez à Paramètres > Paramètres pour les développeurs > Application mobile.

  3. Importez le certificat PEM APNs.

  4. Importez le certificat PEM VoIP.

Demander des autorisations de notification push

Le SDK fournit une méthode pratique pour demander l'autorisation des notifications push (iOS uniquement) :

let options: UNAuthorizationOptions = [.alert, .sound]
try await CCAI.shared.pushNotificationService?.registerForPushNotifications(
    options: options
) { granted in
    if granted {
        print("Push notifications granted")
    } else {
        print("Push notifications denied")
    }
}

S'inscrire aux notifications à distance

Implémentez les délégués de notification push dans votre AppDelegate. Le SDK fournit pushNotificationService pour transférer les jetons :

import CCAIKit

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        CCAI.shared.pushNotificationService?.updatePushToken(
            data: deviceToken,
            type: .apns
        )
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        CCAI.shared.pushNotificationService?.updatePushToken(
            data: nil,
            type: .apns
        )
    }
}

Veillez également à activer Capabilities > Push notifications (Fonctionnalités > Notifications push) et Background modes (Modes d'arrière-plan) pour les notifications à distance dans l'onglet Target > Signing & capabilities (Cible > Signature et fonctionnalités) d'Xcode.

Gérer les notifications push entrantes

Lorsque vous recevez une charge utile push, transférez-la au SDK pour traitement :

func application(
    _ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
    // Let the SDK determine if this push relates to an incoming call,
    // session update, or another internal event
    CCAI.shared.pushNotificationService?.handlePushNotification(userInfo)
    completionHandler(.newData)
}

Le SDK détermine si la notification push concerne un appel entrant, une mise à jour de session ou un autre événement interne, et la traite en conséquence.

S'inscrire aux notifications push VoIP

Pour les appels entrants et certaines actions intelligentes basées sur le réseau téléphonique public commuté, le SDK nécessite un jeton de notification push VoIP en plus du jeton APNs standard. Les notifications push VoIP sont envoyées avec une priorité élevée et peuvent réactiver votre application à partir d'un état arrêté.

Importez PushKit et créez un PKPushRegistry dans le didFinishLaunchingWithOptions de votre AppDelegate.

import PushKit

class AppDelegate: NSObject, UIApplicationDelegate, PKPushRegistryDelegate {

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

        // ... SDK initialization ...

        // Register for VoIP push notifications
        let voipRegistry = PKPushRegistry(queue: DispatchQueue.main)
        voipRegistry.delegate = self
        voipRegistry.desiredPushTypes = [.voIP]

        return true
    }
}

Implémentez les méthodes PKPushRegistryDelegate pour transférer les jetons et les charges utiles VoIP au SDK.

// MARK: - PKPushRegistryDelegate

func pushRegistry(
    _ registry: PKPushRegistry,
    didUpdate credentials: PKPushCredentials,
    for type: PKPushType
) {
    // Forward the VoIP token to the SDK
    CCAI.shared.pushNotificationService?.updatePushToken(
        data: credentials.token,
        type: .voip
    )
}

func pushRegistry(
    _ registry: PKPushRegistry,
    didInvalidatePushTokenFor type: PKPushType
) {
    // Clear the VoIP token
    CCAI.shared.pushNotificationService?.updatePushToken(
        data: nil,
        type: .voip
    )
}

func pushRegistry(
    _ registry: PKPushRegistry,
    didReceiveIncomingPushWith payload: PKPushPayload,
    for type: PKPushType,
    completion: @escaping () -> Void
) {
    guard type == .voIP else {
        completion()
        return
    }

    // Forward the VoIP payload to the SDK using the `handleVoIPPush`
    // extension on `CCAI` (defined in the `CCAICall` module). The SDK
    // takes ownership of the call lifecycle; invoke PushKit's completion
    // handler in the trailing closure after the SDK has accepted the
    // payload.
    CCAI.shared.handleVoIPPush(payload: payload.dictionaryPayload) { completion() }
}