无头移动 SDK (iOS):使用入门

本文档介绍了如何开始使用适用于 iOS 的无头移动 SDK。

借助适用于 iOS 的无头移动 SDK,您可以将 CCAI 平台支持直接添加到自己的内置 iOS 体验中,而无需使用预构建的 CCAI 平台界面。

您无需展示 CCAI Platform 品牌的 widget,而是:

  • 将 CCAI iOS SDK 集成到您的应用中。

  • 构建您自己的界面、导航和视觉设计。

  • 使用 SDK 可执行以下操作:

    • 开始和管理聊天会话和语音会话。

    • 提供电子邮件、预定通话和分流选项。

    • 支持智能操作、附件和会话后流程(客户满意度 [CSAT]、调查问卷、虚拟客服)。

CCAI Platform 可处理联络中心逻辑(路由、队列、渠道、配置、报告)。您拥有整个应用体验(界面、流程和品牌)。

如果您不想自行构建界面,而是希望使用预建的可配置界面,请使用标准移动 SDK。

要求

以下是集成 Headless Mobile SDK for iOS 的要求。

iOS 平台要求

  • iOS 最低版本:iOS 16.6 或更高版本

  • 语言:Swift

  • IDE:您的应用支持的最新稳定版 Xcode

  • 架构:当前 Xcode 工具链支持的标准 iOS 架构。

应用权限

由于您的内置应用会处理智能操作(拍摄照片、录制视频或验证身份)的界面,因此您必须声明 Apple 的强制性隐私权限。

向 Info.plist 添加以下键,并说明您的应用如何使用相应数据:

  • NSCameraUsageDescription:用户拍摄和发送照片/视频或执行代理请求的智能操作时需要此权限。

  • NSMicrophoneUsageDescription:即时语音通话和录制有声视频时需要此权限。

  • NSPhotoLibraryUsageDescription:用户上传相机胶卷中的现有照片或视频时需要此权限。

  • NSFaceIDUsageDescription:如果您支持生物识别验证智能操作,则必须提供此值。

CCAI Platform 实例要求

您需要访问 CCAI 平台实例的开发者设置,才能获取以下详细信息:

  • 公司密钥

  • 公司密码

  • 主机网址 - CCAI 平台的主机名(例如 your_subdomain.ccaiplatform.com)

这些值用于:

  • 公司密钥 + 主机网址:在 iOS 应用中初始化 SDK。

  • 公司密钥:在后端用于对 JSON Web 令牌 (JWT) 进行签名,以对用户进行身份验证。

推送通知

对于通话和某些智能操作,您需要:

  • 针对标准推送通知的 Apple 推送通知服务 (APNs) 配置。

  • 用于支持来电和特定通话相关通知的 VoIP 服务证书。

您将学习以下内容:

  • 在 Apple Developer 门户中配置 APNs 和 VoIP 凭据。

  • 在 CCAI 平台管理员门户中,依次前往设置 > 开发者设置 > 移动应用,上传转换后的证书。

  • 在应用中注册接收远程通知和 VoIP 推送。

  • 将相应的设备令牌转发给 SDK。

  • 在目标 > 签名和功能中,启用以下内容:

    • 推送通知功能。

    • 后台模式功能,并选中以下项:

      • IP 语音 - 对于 VoIP 推送通知(来电、使用 PSTN 的智能操作)是必需的。

      • 音频、AirPlay 和画中画 - 当应用在后台运行时,需要此权限才能继续进行语音通话。

      • 远程通知 - 标准 APNs 推送传送所必需。

无头 iOS SDK 如何融入您的应用

iOS 版无头移动 SDK 可以视为三个层:

CCAI Platform 平台和 SDK

该 SDK 可与 CCAI Platform 平台安全地互动,并提供以下功能:

  • 使用 JSON Web 令牌 (JWT) 进行身份验证。

  • 队列和渠道路由元数据(即时通讯、语音、预定通话、电子邮件、外部分流链接)。

  • 实时会话状态管理。

  • 用于智能操作、文件附件和屏幕共享的安全传输流水线。

  • 分流逻辑和会话后层次结构评估。

您的 iOS 应用(界面和逻辑由您处理)

您可以控制整个视觉和导航体验:

  • 入口点:例如帮助标签页或与我们联系按钮。

  • 菜单:您如何显示队列(例如结算、技术支持)。

  • 会话内界面:自定义聊天气泡、通话界面和控件。

  • 应用的功能:调用 SDK API 来开始和结束会话、监听 SDK 事件,以及驱动视图控制器导航。

CCAI 平台管理员门户配置

CCAI 平台管理员配置互动规则(营业时间、可用渠道、等待时间阈值和调查问卷)。SDK 会读取此配置,并以原始数据和事件的形式将其公开给您的应用。您可以决定如何在屏幕上绘制该配置。

检索公司凭据

在集成 SDK 之前,请从 CCAI 平台实例获取凭据:

  1. 使用管理员账号登录 CCAI 平台管理员门户。

  2. 依次前往设置 > 开发者设置。

  3. 在公司密钥和密钥令牌下方,复制以下内容:

    • 公司密钥

    • 公司密码

  4. 记下您的主机网址:CCAI 平台的主机名(例如 your_subdomain.ccaiplatform.com)。

您将使用:

  • 在 iOS 应用中:公司密钥 + 主机网址。

  • 在后端服务器中:用于为 JWT 签名的公司密钥代码:

    • 最终用户身份验证。

    • CCAI Platform 使用的可选自定义数据或上下文。

将 SDK 添加到您的 iOS 应用

无头 SDK 采用模块化架构构建。您需要安装核心 CCAIKit 以及应用所需的特定功能模块(例如 CCAIChat 或 CCAIScreenShare)。

如需在应用中集成 iOS SDK,请按以下步骤操作。

  1. 请将以下内容添加到 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. 在 AppDelegate 文件中导入 CCAIKit:

    import CCAIKit
    
  3. 如果您的项目是 SwiftUI 项目,请在 App 结构体中声明 @UIApplicationDelegateAdaptor:

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

初始化无头 SDK

在应用启动期间初始化 SDK。由于 SDK 采用模块化架构,您必须先初始化核心 CCAI 系统,然后注册特定的服务模块(例如聊天或屏幕共享)。

基本初始化

SDK 使用一个 InitOptions 结构体来捆绑所有必需的配置:

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 ...
}

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
}

可用的 SDK 服务

初始化后,您可以通过 CCAI.shared singleton 访问各种服务:

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

提取公司配置

初始化后,使用 companyService 获取公司级配置。这对于填充语言选择器、显示公司名称或在用户进入队列之前读取支持联系详情非常有用。

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

对最终用户进行身份验证

Headless Mobile SDK 使用 JSON Web 令牌 (JWT) 对用户进行身份验证,并将情境信息安全地传递给客服人员的 CRM。

工作原理

该 SDK 使用简化的两步异步身份验证流程:

  1. SDK 确定需要对用户进行身份验证。

  2. 它会调用您的 CCAIDelegate 方法 - ccaiShouldAuthenticate() - 这是一个异步函数。

  3. 您的应用使用公司密钥在后端服务器上远程签署 JWT。

  4. 然后,您的应用会将已签名的 JWT 传递给 CCAI.shared.authService?.authenticate(jwt),以换取身份验证令牌。

  5. 身份验证令牌会返回到 SDK 以完成连接。

实现用于身份验证的 CCAIDelegate

您的应用必须实现 CCAIDelegate 协议。当 SDK 需要身份验证时,它会调用单个异步委托方法。

CCAIDelegate 协议

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

实现示例 (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)
    }
}

包含 JWT 签名的完整示例,供参考和测试

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)
    }
}

将自定义数据传递给 CRM

如果您想将情境数据传递给代理(例如用户的当前设备操作系统、位置或账号层级),您的后端必须在对 JWT 载荷进行签名之前将 custom_data 对象注入其中。

每条自定义数据都必须采用 JSON 对象格式,其中包含 label(代理看到的内容)、value 和 type。

支持的数据类型

  • string:标准文本(例如“iPhone 14 Pro”)。

  • number:整数或浮点数(例如 1234 或 99.99)。

  • date:一个 13 位数的 UTC Unix 时间戳,包含毫秒(例如 1537399655992)。

  • url:标准 HTTP/HTTPS 网址格式。

  • boolean:标准 true 或 false 值。

预留的 CRM 键

CCAI Platform 支持特定的预留键,这些键可触发平台中的内置行为,例如将用户标记为 VIP 或向客服人员发出有关作恶方的警告。这些必须采用布尔类型格式,并且只有在载荷使用安全的 JWT 方法签名时才会被接受。

  • reserved_verified_customer:表示客户是否已通过您的内部系统成功完成身份验证。

  • reserved_bad_actor:向代理标记用户,表明其可能是垃圾信息发送者或欺诈性账号。

  • reserved_repeat_customer:表示相应客户最近是否经常与支持人员联系。

包含预留键的载荷示例

{
  "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"
    }
  }
}

管理身份验证令牌

该 SDK 还提供了一些方法,用于手动更新或清除缓存的身份验证令牌:

// 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)

自定义数据 JWT 载荷架构

当后端构建要使用公司密钥进行签名的最终载荷时,必须严格遵循此架构。请注意强制性 iat(签发时间)和 exp(失效时间)时间戳。

{
  "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"
    }
  }
}

启用推送通知

该 SDK 使用推送通知来执行以下操作:

  • 来电。

  • 某些智能操作和与通话相关的事件。

  • 在应用处于后台时保持状态。

配置证书

CCAI 平台要求在 CCAI 平台管理员门户中保存 两种不同类型的 Apple 推送证书:

  • Apple 推送通知服务 (APNs) SSL 证书 - 用于标准远程推送通知。

  • VoIP 服务证书 - 用于来电通知和某些与通话相关的智能操作。

这两个证书均在 Apple Developer 门户中创建,转换为 PEM 格式,然后上传到 CCAI Platform 管理员门户。

创建 APNs SSL 证书

请按照以下步骤(根据 Apple 的官方指南建立基于证书的 APNs 连接)创建 APNs SSL 证书。

  1. 登录 developer.apple.com,然后前往证书、标识符和个人资料。

  2. 选择 + 按钮以创建新证书。

  3. 在“服务”下,选择 Apple 推送通知服务 SSL(沙盒和生产环境)。

  4. 选择与应用的软件包标识符匹配的应用 ID,然后选择继续。

  5. 上传通过 Mac 上的“钥匙串访问”生成的证书签名请求 (CSR):

    • 依次打开钥匙串访问 > 证书助理 > 从证书颁发机构请求证书…

    • 输入您的电子邮件地址,将“CA 电子邮件地址”留空,选择保存到磁盘,然后选择继续。

  6. 选择继续,然后下载生成的 .cer 文件。

  7. 双击 .cer 文件,将其安装到钥匙串中。

  8. 在“钥匙串访问”中,找到已安装的证书,右键点击该证书,然后选择导出…以将其保存为 .p12 文件。系统会提示您设置导出密码。

  9. 使用 openssl 将 .p12 文件转换为 PEM 格式:

    # 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
    

创建 VoIP 服务证书

借助 VoIP 服务证书,您的应用可以通过 Apple 的 PushKit 框架接收高优先级推送通知,从而可以在应用处于终止状态时将其唤醒以处理来电。如需查看最新的官方步骤,请参阅 Apple 开发者文档中的创建 VoIP 服务证书。

  1. 登录 developer.apple.com,然后前往证书、标识符和个人资料。

  2. 选择 + 按钮以创建新证书。

  3. 在“服务”下,选择 VoIP 服务证书。

  4. 选择与应用的软件包标识符匹配的应用 ID,然后选择继续。

  5. 上传您为 APNs SSL 证书生成的同一证书签名请求 (CSR)(或创建新的 CSR)。

  6. 选择继续,然后下载生成的 .cer 文件。

  7. 双击 .cer 文件,将其安装到钥匙串中。

  8. 在“钥匙串访问”中,找到已安装的 VoIP 服务证书,右键点击该证书,然后选择导出…以将其保存为 .p12 文件。

  9. 使用 openssl 将 .p12 文件转换为 PEM 格式:

    # 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
    

将证书上传到 CCAI 平台管理员门户

  1. 登录 CCAI 平台管理员门户。

  2. 依次前往设置 > 开发者设置 > 移动应用。

  3. 上传 APNs PEM 证书。

  4. 上传 VoIP PEM 证书。

请求推送通知权限

该 SDK 提供了一种便捷方法来请求推送通知授权(仅限 iOS):

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")
    }
}

注册接收远程通知

在 AppDelegate 中实现推送通知委托。SDK 提供了用于转发令牌的 pushNotificationService:

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
        )
    }
}

请务必在 Xcode 中通过 Target > Signing & capabilities(目标 > 签名和功能)标签页为远程通知启用功能 > 推送通知和后台模式。

处理传入的推送通知

收到推送载荷后,将其转发给 SDK 进行处理:

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)
}

SDK 会确定推送是否与来电、会话更新或其他内部事件相关,并相应地处理推送。

注册接收 VoIP 推送通知

对于来电和某些 PSTN 智能操作,除了标准 APNs 令牌之外,SDK 还需要 VoIP 推送令牌。VoIP 推送以高优先级传送,并且可以从终止状态唤醒应用。

导入 PushKit 并在 AppDelegate 的 didFinishLaunchingWithOptions 中创建 PKPushRegistry

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
    }
}

实现 PKPushRegistryDelegate 方法,以将 VoIP 令牌和载荷转发到 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() }
}