Guía del SDK de iOS

El SDK para dispositivos móviles de Contact Center AI Platform (CCAI Platform) para el sistema operativo iOS de Apple proporciona la capacidad de incorporar la experiencia móvil de CCAI Platform en las aplicaciones para dispositivos móviles con iOS.

Requisitos

El SDK para iOS tiene los siguientes requisitos:

  • iOS 12.0 o versiones posteriores

Recupera las credenciales de la empresa

  1. Accede al portal de Contact Center AI Platform (CCAI Platform) con las credenciales de administrador.

  2. Ve a Configuración > Configuración para desarrolladores.

  3. En Clave de la empresa y Código secreto, anota la clave de la empresa y el código secreto de la empresa.

Cómo comenzar

A continuación, se proporciona una guía para comenzar a usar el SDK para dispositivos móviles de iOS de CCAI Platform.

Instalación

Para comenzar, debes instalar el SDK de iOS.

Descarga la app de ejemplo

  1. Descarga la app de ejemplo para iOS.

  2. Navega a la carpeta y, luego, instala las dependencias con CocoaPods:

    $ pod install --project-directory=ExampleApp
    
  3. Para configurar rápidamente la configuración del proyecto, ejecuta una secuencia de comandos de shell:

    $ ./setup.sh
    

    Como alternativa, puedes editar de forma manual la configuración del proyecto siguiendo estos pasos:

    1. Abre ExampleApp.xcworkspace.

    2. Reemplaza los valores UJETCompanyKey y UJETCompanySecret en Info.plist por los valores Clave de la empresa y Código secreto de la empresa de la página Configuración > Configuración de desarrollador en el portal de CCAI Platform.

    3. Reemplaza el valor UJETSubdomain en Info.plist por el subdominio en la URL de tu portal de CCAI Platform. El subdominio precede directamente a .ujet.com en la URL; por ejemplo, your-subdomain en https://your-subdomain.ujet.com/settings/developer-setting.

Integra el SDK en tu proyecto

La integración del SDK de iOS con tu aplicación depende de tu entorno de desarrollo.

Swift Package Manager

  1. Agrega el paquete de Swift para el SDK de iOS desde GitHub.

  2. En la configuración de compilación, coloca -ObjC en Other Linker Flags.

  3. A partir de Xcode 13.2, existe un problema conocido con el consumo de frameworks binarios distribuidos con Swift Package Manager. La solución alternativa actual para este problema es agregar una fase de ejecución de secuencia de comandos a las fases de compilación de tu proyecto de Xcode. Esta fase de ejecución de la secuencia de comandos debe realizarse después de la fase de compilación de Embed Frameworks. Esta nueva fase de ejecución de la secuencia de comandos debería contener el siguiente código:

    find "${CODESIGNING_FOLDER_PATH}" -name '*.framework' -print0 | while read -d $'0' framework
    do
    codesign --force --deep --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements --timestamp=none "${framework}"
    done
    

CocoaPods

  1. Agrega la siguiente línea al Podfile:

    pod 'UJET', :podspec =>
    'https://sdk.ujet.co/ios/x.y.z/ujet.podspec' #specific version
    x.y.z
    
  2. Ejecuta pod install. Si el SDK de iOS se integró anteriormente, ejecuta pod update CCAI Platform en su lugar.

Carthage

Google Cloud recomienda usar un administrador de dependencias o una integración manual, ya que una dependencia de CCAI Platform no admite Carthage.

Para usar un administrador de dependencias diferente, sigue estos pasos:

  1. Agrega estas líneas:

    binary "https://sdk.ujet.co/ios/UJETKit.json"
    binary "https://sdk.ujet.co/ios/UJETFoundationKit.json"
    binary "https://sdk.ujet.co/ios/UJETCallRedKit.json"
    binary "https://sdk.ujet.co/ios/UJETChatRedKit.json"
    # optional
    # binary "https://sdk.ujet.co/ios/UJETChatBlueKit.json"
    binary "https://raw.githubusercontent.com/twilio/twilio-voice-ios/Releases/twilio-voice-ios.json"
    # this is not supported: https://github.com/twilio/conversations-ios/issues/12
    # binary "https://raw.githubusercontent.com/twilio/conversations-ios/master/twilio-convo-ios.json"
    
  2. Ejecuta carthage bootstrap --use-xcframeworks (o carthage update --use-xcframeworks si actualizas las dependencias).

Integración manual
  1. Descarga UJETKit.xcframework, UJETFoundationKit.xcframework, UJETChatRedKit.xcframework, UJETChatBlueKit.xcframework, UJETTwilioCallKit.xcframework y todas las dependencias TwilioVoice.xcframework y TwilioConversationsClient.xcframework.

  2. Agrega el archivo UJETKit.xcframework a tu destino arrastrándolo a la sección Frameworks, Libraries, and Embedded Content.

  3. Repite los pasos 2 y 3 en todas las dependencias del paso 1.

  4. En Build Settings, coloca -ObjC en Other Linker Flags.

  5. Agrega libc++.tbd como dependencia en la sección Linked Frameworks del destino.

Si vas a compilar el SDK de forma manual con el proyecto de ejemplo, sigue los pasos que se indican en la siguiente sección.

Compila el SDK de forma manual con el proyecto de ejemplo

Sigue estos pasos en orden:

  1. Descarga todos los frameworks, incluidos UJETKit.xcframework y otras dependencias.

  2. Crea la carpeta CCAI Platform en la raíz del proyecto y extrae todos los frameworks.

  3. Selecciona el destino Objc-Manual o Swift-Manual y compila.

Importa el framework

En las siguientes secciones, se proporcionan instrucciones para importar el framework.

Proyecto de Objective-C

@import UJETKit;

Proyecto de Swift

swiftimport
UJETimport UJETKit

Inicializa el SDK

Inicializa CCAI Platform con UJET_COMPANY_KEY y UJET_SUBDOMAIN.

In application:didFinishLaunchingWithOptions: method:
-   (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // Initialize CCAI Platform
    [UJET.initialize:UJET_COMPANY_KEY subdomain:UJET_SUBDOMAIN delegate:self];

    // YOUR CODE

    return YES;
}

Puedes cambiar el nivel de registro de verbose a error. El nivel de registro predeterminado es UjetLogLevelInfo.

[UJET.setLogLevel:UjetLogLevelVerbose];

Autenticación del usuario final

Accede al SDK de iOS a través de la app para iOS.

Para asegurarnos de que el usuario final esté autenticado, presentamos el mecanismo de firma de JWT.

El SDK para iOS solicitará que se firme la carga útil cuando se necesite autenticación. Si la firma se realiza de forma correcta, la aplicación intercambia el JWT firmado por el token de autenticación del usuario final. Se debe llamar al bloque de éxito o error antes de que se devuelva el delegado.

En el caso de los usuarios anónimos (identificador = nulo), la aplicación creará un UUID para el usuario. Si, más adelante, el usuario se autentica con un identificador, la aplicación intentará combinar los dos usuarios en función del UUID.

En UJETObject.h del proyecto de ejemplo:

@import UJETKit;

@interface UJETObject : NSObject <UJETDelegate>

Implementa el método delegado signPayload: payloadType: success: failure:.

-   (void)signPayload:(NSDictionary *)payload payloadType:(UjetPayloadType)payloadType success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure {
  if (payloadType == UjetPayloadAuthToken) {
    [self signAuthTokenInLocal:payload success:success failure:failure];
  }
}

-   (void)signAuthTokenInLocal:(NSDictionary *)payload success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure {
    NSMutableDictionary *payloadData = [payload mutableCopy];

    NSDictionary *userData = [[NSUserDefaults standardUserDefaults] objectForKey:@"user-data"];
    [payloadData addEntriesFromDictionary:userData];
    payloadData[@"iat"] = [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]]; // required
    payloadData[@"exp"] = [NSNumber numberWithDouble:([[NSDate date] timeIntervalSince1970] + 600)]; // required

    NSString *signedToken = [self encodeJWT:payloadData];

    if (signedToken.length > 0) {
        success(signedToken);

    } else {
        NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Failed to sign token" };
        NSError *error = [NSError errorWithDomain:@"ExampleApp" code:0 userInfo:userInfo];
        failure(error);
    }
}

-   (NSString *)encodeJWT:(NSDictionary *)payload {
    id<JWTAlgorithm> algorithm = [JWTAlgorithmHSBase algorithm384];
    NSString *secret = NSBundle.mainBundle.infoDictionary[@"UJETCompanySecret"];
    return [JWTBuilder encodePayload:payload].secret().algorithm(algorithm).encode;
}

Te recomendamos que firmes la carga útil desde el servidor de la aplicación, no en el cliente.

En este ejemplo, se usa la firma local con fines de prueba. Consulta signDataInRemote: success: failure: en el archivo UJETObject.m.

Para obtener más información, consulta Autenticación de usuarios finales del SDK.

Cómo configurar las notificaciones push

La plataforma de CCAI envía notificaciones push para solicitar acciones inteligentes, como la verificación y la foto, así como para informar una llamada entrante. Si tus colas de chat usan la función de registro (requiere el SDK de iOS 2.16.0 o una versión posterior), CCAI Platform envía una notificación push para solicitarles a los consumidores que esperan que vuelvan al chat y confirmen que están listos. Consulta Check In para el SDK de dispositivos móviles. La plataforma de CCAI requiere que se guarden dos tipos diferentes de certificados (VoIP y APNs) en el portal de administración de la plataforma de CCAI.

Configura certificados

CCAI Platform requiere que se guarden dos tipos diferentes de certificados push de Apple en el portal de administración de CCAI Platform:

  • Un certificado SSL del servicio de notificaciones push de Apple (APNs), que se usa para las notificaciones push remotas estándar

  • Un certificado de servicios de VoIP, que se usa para las notificaciones de llamadas entrantes y ciertas acciones inteligentes relacionadas con las llamadas

Ambos certificados se crean en el portal para desarrolladores de Apple, se convierten al formato PEM y, luego, se suben al portal de administración de CCAI Platform.

Crea un certificado SSL de APNs

Sigue estos pasos basados en la guía oficial de Apple Establishing a Certificate-Based Connection to APNs para crear un certificado SSL de APNs.

  • Accede a developer.apple.com y navega a Certificates, identifiers & profiles.

  • Haz clic en el botón + para crear un certificado nuevo.

  • En Services, selecciona Apple push notification service SSL (Sandbox & production).

  • Elige el ID de app que coincida con el identificador de paquete de tu aplicación y, luego, haz clic en Continuar.

  • Sube una solicitud de firma de certificado (CSR) generada desde el acceso a llaveros en tu Mac:

    • Abre Acceso a llaveros > Asistente de certificados > Solicitar un certificado de una autoridad certificadora.

    • Ingresa tu dirección de correo electrónico, deja vacía la dirección de correo electrónico de la AC, selecciona Guardado en el disco y haz clic en Continuar.

  • Haz clic en Continuar y, luego, en Descargar el archivo .cer generado.

  • Haz doble clic en el archivo .cer para instalarlo en tu llavero.

  • En Acceso a llaveros, busca el certificado instalado, haz clic con el botón derecho en él y selecciona Exportar… para guardarlo como un archivo .p12. Se te solicitará que establezcas una contraseña de exportación.

  • Convierte el archivo .p12 al formato PEM con 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

Crea un certificado de servicios de VoIP

Los certificados de servicios de VoIP permiten que tu app reciba notificaciones push de alta prioridad a través del framework de PushKit de Apple, que puede activar tu app desde un estado finalizado para controlar las llamadas entrantes. Para conocer los pasos oficiales más recientes, consulta Crea certificados de servicios de VoIP en la documentación para desarrolladores de Apple.

  • Accede a developer.apple.com y navega a Certificates, Identifiers & Profiles.

  • Haz clic en el botón + para crear un certificado nuevo.

  • En Servicios, selecciona Certificado de servicios de VoIP.

  • Elige el ID de app que coincida con el identificador de paquete de tu aplicación y, luego, haz clic en Continuar.

  • Sube la misma solicitud de firma de certificado (CSR) que generaste en la sección anterior (o crea una nueva).

  • Haz clic en Continuar y, luego, en Descargar el archivo .cer generado.

  • Haz doble clic en el archivo .cer para instalarlo en tu llavero.

  • En Acceso a llaveros, busca el certificado de servicios de VoIP instalado, haz clic con el botón derecho en él y selecciona Exportar… para guardarlo como un archivo .p12.

  • Convierte el archivo .p12 al formato PEM con 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

Sube certificados al portal de administración de CCAI Platform

  • Accede al portal de administración de CCAI Platform.

  • Navega a Configuración > Configuración para desarrolladores > App para dispositivos móviles.

  • Sube el certificado PEM de APNs.

  • Sube el certificado PEM de VoIP.

Habilita las notificaciones push

  1. Selecciona tu destino y abre la pestaña Capabilities.

  2. Activa el interruptor de Notificaciones push.

Prueba las notificaciones push

En las siguientes secciones, se proporcionan instrucciones para probar las notificaciones push.

Sección de depuración de notificaciones push

En el portal de administrador, navega a Configuración > Configuración para desarrolladores. En esta página, busca la sección titulada Depuración de notificaciones push:

Copia y pega el token del dispositivo en el área de texto de la derecha y selecciona la app para dispositivos móviles correcta.

Obtén el token de dispositivo

Un ejemplo de cadena de token de dispositivo se ve de la siguiente manera:

7db0bc0044c8a203ed87cdab86a597a2c43bf16d82dae70e8d560e88253364b7

Las notificaciones push suelen configurarse en la clase que se ajusta al protocolo UIApplicationDelegate o PKPushRegistryDelegate. En algún momento, el token de dispositivo estará disponible para ti. Puedes imprimirlo antes de pasarlo al SDK de iOS. Para obtener el token de tu dispositivo, usa el fragmento de código.

Swift
func tokenFromData(data: Data) -> String {
  return data.map { String(format: "%02x", $0) }.joined()
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  print("apns token: ", tokenFromData(data: deviceToken))
  ...
}

func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
  print("voip token: ", tokenFromData(data: credentials.token))
  ...
}
Obj-C
-   (NSString *)tokenFromData:(NSData *)data {
  const char *d = data.bytes;
  NSMutableString *token = [NSMutableString string];

  for (NSUInteger i = 0; i < data.length; i++) {
    [token appendFormat:@"%02.2hhX", d[i]];
  }

  return [[token copy] lowercaseString];
}

-   (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
  NSLog(@"voip token: %@", [self tokenFromData:credentials.token]);
  ...
}

-   (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  NSLog(@"apns token: %@", [self tokenFromData:deviceToken]);
}
Resultado

Una vez que hayas ingresado el archivo PEM del certificado y el token del dispositivo, haz clic en el botón.

El resultado mostrará el mensaje Notificación push configurada correctamente si la notificación push de prueba se entregó correctamente.

No se garantiza la entrega de una notificación push, ya que depende de la conexión de red del dispositivo.

Parámetros de configuración del proyecto

En las siguientes secciones, se describen los cambios necesarios para configurar el proyecto.

Funciones

En la configuración de destino, activa las siguientes funciones:

  • Notificaciones push

  • Modos en segundo plano (marca estos elementos)

  • Audio y AirPlay

  • Voz por IP

Info.plist

Para proteger la privacidad del usuario, cualquier app para iOS vinculada en iOS 10.0 o versiones posteriores que acceda a cualquiera de los micrófonos, la biblioteca de fotos y la cámara del dispositivo debe declarar la intención de hacerlo. Incluye las siguientes claves con un valor de cadena en el archivo Info.plist de tu app y proporciona una cadena de propósito para esta clave. Si tu app intenta acceder a cualquiera de los micrófonos, la biblioteca de fotos y la cámara del dispositivo sin una cadena de propósito correspondiente, la app se cierra.

  • NSMicrophoneUsageDescription: Permite el acceso al micrófono para llamar y hablar con los equipos de asistencia o solución de problemas, y para enviar videos con sonido relacionados con consultas sobre productos.

  • NSCameraUsageDescription: Permite el acceso a la cámara para que el cliente tome y envíe fotos relacionadas con su consulta de asistencia al cliente.

  • NSPhotoLibraryUsageDescription: Permite que el cliente acceda para enviar fotos relacionadas con su consulta de asistencia al cliente.

  • NSFaceIDUsageDescription: Permite el acceso a la verificación con Face ID.

Inicia el SDK de iOS

Agrega la siguiente línea donde quieras iniciar el SDK de iOS:

[UJET startWithOptions:nil];

También puedes iniciar el SDK de iOS desde un punto específico del menú con esta clave usando un punto de acceso directo:

UJETStartOptions *option = [[UJETStartOptions alloc] initWithMenuKey:@"MENU_KEY"];
[UJET startWithOptions:option];

El menuKey se puede crear con un punto de acceso directo (DAP). En los siguientes pasos, se proporcionan instrucciones para crear una DAP:

  1. Accede al portal de CCAI Platform con las credenciales de administrador.

  2. Ve a Configuración > Cola.

  3. Selecciona cualquier cola de la estructura del menú.

  4. Selecciona Crear punto de acceso directo.

  5. Ingresa la clave en el formulario de texto.

  6. Haz clic en Guardar.

Borra la caché del almacenamiento local si se actualizaron los datos del usuario

Almacenamos en caché el token de autenticación en el llavero para volver a usarlo y realizar solicitudes menos frecuentes para firmar la carga útil desde la app host. El SDK lo usará hasta que venza o se revoque a través de la llamada a clearUserData. La app host se encarga de revocar esta caché cada vez que se modifican o actualizan los datos relacionados con el usuario, como un evento de salida.

[UJET clearUserData];

Verifica si hay una sesión existente antes de iniciar Contact Center AI Platform

Antes de iniciar una sesión, verifica que no haya una sesión actual. Esto es especialmente importante cuando cambia el userId.

[UJET getStatus];

Si hay una sesión existente, debemos pedirle al usuario que la reanude o cancele la acción:

if ([UJET getStatus] != UjetStatusNone) {
  // Display alert to cancel login or resume existing session
}

Personalizar

Existen varias opciones para el tema del SDK que se indican en UJETGlobalTheme.h.

Establece tu tema después de [UJET initialize], por ejemplo:

UJETGlobalTheme *theme = [UJETGlobalTheme new];

theme.font = [UIFont fontWithName:@"OpenSans" size: 16.0f];
theme.lightFont = [UIFont fontWithName:@"OpenSans-Light" size: 16.0f];
theme.boldFont = [UIFont fontWithName:@"OpenSans-Bold" size: 16.0f];
theme.tintColor = [UIColor colorWithRed:0.243 green:0.663 blue:0.965 alpha:1.00];

[Ujet setGlobalTheme:theme];

El nombre de la empresa se recupera de Admin Portal > Settings > Support Center Details > Nombre visible.

Puedes configurar la imagen del logotipo en lugar del nombre de la empresa de la siguiente manera:

theme.companyImage = [UIImage imageNamed:@"logo"];

Si la imagen es demasiado grande, se cambiará su tamaño para que quepa en el área.

Strings

También puedes personalizar las cadenas anulando el valor. Por ejemplo, coloca este par clave-valor en tu archivo Localizable.strings:

"ujet_greeting_title" = "Title";

"ujet_greeting_description" = "Description";

Las cadenas personalizables disponibles se enumeran en el archivo ujet.strings.

Modo oscuro

Puedes especificar un matiz del color que quieras para el modo oscuro para mejorar la legibilidad de las fuentes.

@property (nonatomic, strong) UIColor \*tintColorForDarkMode;

Si no configuras la propiedad, se usará UJETGlobalTheme.tintColor para el modo oscuro. Te recomendamos que configures esta propiedad si tu app admite el modo oscuro. Consulta los siguientes artículos de Apple para saber cómo elegir el color de tono adecuado para el modo oscuro:

Tema del chat

Para personalizar la pantalla de chat, tienes la opción de usar una cadena JSON o cada clase de tema.

Como referencia, consulta la app de ejemplo y quita el comentario del método customizeChatTheme.

func customizeChatTheme() throws {
  guard let file = Bundle.main.path(forResource: "chat-theme-custom", ofType: "json") else { return }
  let json = try String.init(contentsOfFile: file, encoding: .utf8)

  let chatTheme = UJETChatTheme.init(jsonString: json)

  let quickReplyTheme = UJETChatQuickReplyButtonTheme()
  quickReplyTheme.style = .individual
  quickReplyTheme.alignment = .right
  quickReplyTheme.backgroundColor = UJETColorRef(assetName: "white_color")
  quickReplyTheme.backgroundColorForHighlightedState = UJETColorRef(assetName: "quick_reply_color")
  quickReplyTheme.textColor = UJETColorRef(assetName: "quick_reply_color")
  quickReplyTheme.textColorForHighlightedState = UJETColorRef(assetName: "white_color")

  let fontTheme = UJETFontTheme()
  fontTheme.family = "Arial Rounded MT Bold"
  fontTheme.size = 14
  quickReplyTheme.font = fontTheme

  chatTheme?.quickReplyButtonTheme = quickReplyTheme

  let globalTheme = UJETGlobalTheme()
  globalTheme.chatTheme = chatTheme
  globalTheme.defaultAgentImage = UIImage(named: "agent_avatar_image")
  globalTheme.font = UIFont(name: "Arial Rounded MT Bold", size: 14)

  UJET.setGlobalTheme(globalTheme)
}

Tema de las tarjetas de contenido

Puedes agregar la personalización de las tarjetas de contenido junto con la personalización del chat. Puedes hacerlo con el archivo JSON (consulta la propiedad content_card) o con la clase UJETChatContentCardTheme.

func customizeChatTheme() throws {
  guard let file = Bundle.main.path(forResource: "chat-theme-custom", ofType: "json") else { return }
  let json = try String.init(contentsOfFile: file, encoding: .utf8)

  let chatTheme = UJETChatTheme.init(jsonString: json)

  let contentCardTheme = UJETChatContentCardTheme()
  contentCardTheme.backgroundColor = UJETColorRef(assetName: "agent_message_background_color")
  contentCardTheme.cornerRadius = 16

  let contentCardFontTheme = UJETFontTheme()
  contentCardFontTheme.family = "Arial Rounded MT Bold"
  contentCardFontTheme.size = 18
  contentCardTheme.font = contentCardFontTheme

  let contentCardBorder = UJETBorderTheme()
  contentCardBorder.width =  1
  contentCardBorder.color = UJETColorRef(assetName: "agent_message_border_color")
  contentCardTheme.border = contentCardBorder

  let contentCardFontTheme = UJETFontTheme()
  contentCardFontTheme.family = "Arial Rounded MT Bold"
  contentCardFontTheme.size = 18
  contentCardTheme.font = contentCardFontTheme

  // The font family is inherited from the contentCardFontTheme
  let subtitle = UJETFontTheme()
  subtitle.size = 12
  contentCardTheme.subtitle = subtitle

  // The font family is inherited from the contentCardFontTheme
  let bodyFont = UJETFontTheme()
  bodyFont.size = 10
  contentCardTheme.body = bodyFont

  theme.chatTheme?.contentCard = contentCardTheme

  let globalTheme = UJETGlobalTheme()
  globalTheme.chatTheme = chatTheme
  globalTheme.defaultAgentImage = UIImage(named: "agent_avatar_image")
  globalTheme.font = UIFont(name: "Arial Rounded MT Bold", size: 14)

  UJET.setGlobalTheme(globalTheme)
}

Tema de la tarjeta del formulario

Puedes agregar la personalización de las tarjetas de formulario junto con la personalización del chat. Para ello, usa el archivo JSON (consulta form_card property) o la clase UJETChatFormCardTheme.

func customizeChatTheme() throws {
    guard let file = Bundle.main.path(forResource: "chat-theme-custom", ofType: "json") else { return }
    let json = try String.init(contentsOfFile: file, encoding: .utf8)

    let chatTheme = UJETChatTheme.init(jsonString: json)

    let formCardTheme = UJETChatFormCardTheme()
    formCardTheme.backgroundColor = UJETColorRef(assetName: "agent_message_background_color")
    formCardTheme.cornerRadius = 16

    let formCardFontTheme = UJETFontTheme()
    formCardFontTheme.family = "Arial Rounded MT Bold"
    formCardFontTheme.size = 18
    formCardTheme.font = formCardFontTheme

    let formCardBorder = UJETBorderTheme()
    formCardBorder.width =  1
    formCardBorder.color = UJETColorRef(assetName: "agent_message_border_color")
    formCardTheme.border = formCardBorder

    let titleFontTheme = UJETFontTheme()
    titleFontTheme.family = "Arial Rounded MT Bold"
    titleFontTheme.size = 18
    formCardTheme.title = titleFontTheme

    // The font family is inherited from the formCardFontTheme
    let subtitleFontTheme = UJETFontTheme()
    subtitleFontTheme.size = 12
    formCardTheme.subtitle = subtitleFontTheme

    chatTheme?.formCard = formCardTheme

    let globalTheme = UJETGlobalTheme()
    globalTheme.chatTheme = chatTheme
    globalTheme.defaultAgentImage = UIImage(named: "agent_avatar_image")
    globalTheme.font = UIFont(name: "Arial Rounded MT Bold", size: 14)

    UJET.setGlobalTheme(globalTheme)
}

Configuración del formulario web

Para configurar la función de formulario web, implementa el método ujetWebFormDidReceive del protocolo UJETDelegate. Este método recibe un evento (un diccionario FormMessageReceivedEvent) como parámetro, que contiene información relacionada con el formulario. El diccionario de eventos (FormMessageReceivedEvent) incluye la siguiente estructura JSON:

  {
    "type": "form_message_received",
    "smart_action_id": 1,
    "external_form_id": "external_foobar"
    "signature": "4868a7e1dcb5..."
  }

Para controlar el evento, haz lo siguiente:

  1. Extrae la información pertinente del diccionario de eventos (smart_action_id, external_form_id y signature).

  2. Genera un URI de formulario y una firma para los datos del formulario.

  3. Pasa los datos del formulario al SDK como un diccionario FormDataEvent con completion closure.

  4. Si se produce algún error durante la generación del URI o la firma, invoca la devolución de llamada con callback.onError() y Error.

El diccionario (FormDataEvent) que se pasa al SDK debe tener la siguiente estructura:

 {
    "type": "form_data",
    "signature": "4868a7e1dcb5...",
    "data": {
       "smart_action_id":1,
       "external_form_id": "form_id",
       "uri":"foobar"
    }
  }

La firma (HMAC-SHA:256) se debe generar con el data, firmado con la clave secreta compartida. Las claves de objeto de los datos deben ordenarse alfabéticamente antes de generar las firmas, y se debe enviar el mismo data al SDK.

Transferencia posterior a la sesión

Puedes agregar personalización para el VA posterior a la sesión junto con la personalización del chat. Esto se puede lograr con el archivo JSON (consulta la propiedad post_session) o con la clase UJETChatPostSessionVaTheme. El ancho del borde solo puede ser 0 o 1, y si no quieres diferenciar la experiencia del agente virtual posterior a la sesión, puedes establecer containerColor en blanco y el borde en 0.

func customizeChatTheme() throws {
    guard let file = Bundle.main.path(forResource: "chat-theme-custom", ofType: "json") else { return }
    let json = try String.init(contentsOfFile: file, encoding: .utf8)

    let chatTheme = UJETChatTheme.init(jsonString: json)

    let postSessionVaTheme = UJETChatPostSessionVaTheme()
    postSessionVaTheme.containerColor = UJETColorRef(assetName: "white_color")

    let postSessionVaBorder = UJETBorderTheme()
    postSessionVaBorder.width =  0
    postSessionVaBorder.color = UJETColorRef(assetName: "white_color")
    containerColor.border = postSessionVaBorder

    chatTheme?.postSessionVaTheme = postSessionVaTheme

    let globalTheme = UJETGlobalTheme()
    globalTheme.chatTheme = chatTheme

    UJET.setGlobalTheme(globalTheme)
}

Menú de acciones del chat

Puedes agregar la personalización del menú de acciones de chat junto con la personalización del chat. Esto se puede lograr con el archivo JSON (consulta la propiedad form_card) o con la clase UJETChatActionMenuTheme.

func customizeChatTheme() throws {
    guard let file = Bundle.main.path(forResource: "chat-theme-custom", ofType: "json") else { return }
    let json = try String.init(contentsOfFile: file, encoding: .utf8)

    let chatTheme = UJETChatTheme.init(jsonString: json)

    let actionMenuTheme = UJETChatActionMenuTheme()

    let photoLibraryIcon = UJETChatUserInputIconTheme()
    photoLibraryIcon.visible = true
    photoLibraryIcon.image = UJETImageRef(assetName: "library_button_asset")

    let cameraIcon = UJETChatUserInputIconTheme()
    cameraIcon.visible = true
    cameraIcon.image = UJETImageRef(assetName: "camera_button_asset")

    let cobrowseIcon = UJETChatUserInputIconTheme()
    cobrowseIcon.visible = true
    cobrowseIcon.image = UJETImageRef(assetName: "cobrowse_button_asset")

    actionMenuTheme.libraryIcon = photoLibraryIcon
    actionMenuTheme.cameraIcon = cameraIcon
    actionMenuTheme.cobrowseIcon = cobrowseIcon

    chatTheme?.actionMenu = actionMenuTheme

    let globalTheme = UJETGlobalTheme()
    globalTheme.chatTheme = chatTheme

    UJET.setGlobalTheme(globalTheme)
}

Otras apariciones

Puedes personalizar otros aspectos, como el tamaño de la fuente y el color de fondo.

theme.supportTitleLabelFontSize = 30;
theme.supportDescriptionLabelFontSize = 20;
theme.supportPickerViewFontSize = 30;
theme.staticFontSizeInSupportPickerView = YES;

theme.backgroundColor = UIColor.darkGrayColor;
theme.backgroundColorForDarkMode = UIColor.lightGrayColor;

CallKit

En iOS 10.0 y versiones posteriores, CallKit está habilitado para todas las llamadas.

Con CallKit, se muestra una llamada entrante en la app con la pantalla de llamada y se muestra la llamada en el historial de llamadas del teléfono.

Para iniciar una nueva sesión de asistencia de CCAI Platform desde el historial de llamadas, agrega el siguiente bloque a tu AppDelegate.m:

AppDelegate.m:
-   (BOOL)application:(UIApplication *)app continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler {
    if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) {
        // Open app from Call history
        [UJET startWithOptions:nil];
    }

    return YES;
}

CallKit permite que se muestre un ícono de 40 x 40 en la pantalla de bloqueo cuando se recibe una llamada mientras el dispositivo está bloqueado. Coloca una imagen en tus Xcassets con el nombre 'icon-call-kit'.

Configura el SDK

Puedes configurar varias opciones antes de iniciar el SDK.

Consulta la clase UJETGlobalOptions para obtener más detalles.

UJETGlobalOptions *options = [UJETGlobalOptions new];
options.fallbackPhoneNumber = @"+18001112222";
options.preferredLanguage = @"en";

[UJET setGlobalOptions:options];

Botón para mostrar u ocultar la descarga de la transcripción

Puedes configurar el SDK para que muestre o no el botón de descarga de la transcripción en el menú de opciones del chat y en la pantalla posterior al chat.

En el siguiente código, se muestra cómo configurar el botón para descargar la transcripción:

typedef NS_OPTIONS(NSUInteger, UJETChatDownloadTranscriptVisibilityOptions) {
    UJETChatDownloadTranscriptVisibilityOptionsShowAll = 0,
    UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu = 1 << 0,
    UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen = 1 << 1,
    UJETChatDownloadTranscriptVisibilityOptionsHideAll = UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu | UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen
};

@property (nonatomic, assign) UJETChatDownloadTranscriptVisibilityOptions transcriptVisibilityOptions;

Resguardo de RTC

Proporcionamos resguardo de PSTN para varias situaciones:

  • La red móvil está sin conexión.

  • No se puede acceder al backend de la aplicación.

  • La VoIP no está disponible

    • El estado de la red no es lo suficientemente bueno para conectarse. Consulta la propiedad UJETGlobalOptions.pstnFallbackSensitivity para obtener más detalles.

    • Se produjo un error durante la conexión debido a la configuración del firewall o a un problema del proveedor.

Te recomendamos que configures el número de IVR de tu empresa en UJETGlobalOptions.fallbackPhoneNumber. El formato recomendado es +, seguido del código de país y el número de teléfono, p. ej., +18001112222.

Sensibilidad de resguardo de RTC

Puedes ajustar el nivel de sensibilidad de la verificación de las condiciones de la red para el resguardo de PSTN.

@property (nonatomic, assign) float pstnFallbackSensitivity;

El valor debe estar en el rango de 0.0 a 1.0. Si se establece en 1, la llamada siempre se conectará a través de la PSTN en lugar de VoIP. La latencia máxima y el umbral de ancho de banda mínimo son 10,000 ms y 10 KB/s, respectivamente, para el valor de 0. Por ejemplo, un valor de 0.5 significa que la latencia y el ancho de banda mínimos son de 5,000 ms y 15 KB/s, respectivamente.

Para configurar este valor, sigue estos pasos:

  1. Accede al portal de CCAI Platform como administrador.

  2. Ve a Configuración > Configuración para desarrolladores > Apps para dispositivos móviles.

  3. Busca la sección Fallback phone number threshold. El valor predeterminado es 0.85.

  4. Especifica el nuevo valor de umbral.

  5. Haz clic en Guardar.

Cómo desactivar las notificaciones push a nivel global

Puedes desactivar las notificaciones push a nivel global. Si configuras la siguiente propiedad como false, se omiten todas las dependencias de las notificaciones push y se evita que estas lleguen a los usuarios finales:

@property (nonatomic, assign) BOOL allowsPushNotifications;

Ignorar el modo oscuro

Puedes ignorar el modo oscuro en el SDK de CCAI Platform específicamente con esta propiedad:

@property (nonatomic, assign) BOOL ignoreDarkMode;

Ocultar barra de estado

Puedes controlar la visibilidad de la barra de estado con esta propiedad:

  @property (nonatomic, assign) BOOL hideStatusBar;

De forma predeterminada, el hideStatusBar se establece en false y visible.

Omitir la encuesta de CSAT

Puedes agregar un botón que permita al usuario omitir la encuesta de CSAT. En la siguiente muestra de código, se indica cómo agregar el botón:

let options = UJETGlobalOptions()
options.skipCsat = true

Cómo personalizar el indicador de actividad

Puedes agregar tu propia animación de carga (dentro de un UIView) al SDK y anular el UIActivityIndicatorView predeterminado. Implementa el método ujet_activityIndicator de UJETDelegate y devuelve la vista personalizada.

public func ujet_activityIndicator() -> UIView! {
    let loader = UIView.init()
    let animation = CABasicAnimation()
    loader.backgroundColor = .blue
    loader.layer.cornerRadius = 15
    animation.timingFunction = CAMediaTimingFunction.init(name: CAMediaTimingFunctionName.easeOut)
    animation.keyPath = "transform.scale"
    animation.duration = 1.0
    animation.fromValue = 0.0
    animation.toValue = 1.0
    animation.repeatCount = Float.infinity
    animation.isRemovedOnCompletion = false
    loader.layer.add(animation, forKey: "Load")

    return loader
}

Si ya configuraste UIUserInterfaceStyle como Light en el archivo Info.plist de tu app para inhabilitar por completo el modo oscuro, puedes ignorar esta propiedad.

Idioma preferido

El SDK de CCAI Platform usará el siguiente orden de prioridad para determinar el idioma preferido.

  1. Es el idioma seleccionado en la pantalla de presentación de la app.

  2. Se seleccionó el idioma predeterminado de UJETGlobalOptions. Puedes establecer el idioma predeterminado con la propiedad preferredLanguage. Los códigos de idioma admitidos se pueden encontrar en el archivo UJETGlobalOptions.h.

  3. Se usará el idioma seleccionado en el dispositivo (desde Configuración > General > Idioma y región) cuando la app lo admita.

  4. Se usará el dialecto más cercano al idioma del dispositivo cuando la aplicación no admita el idioma del dispositivo, pero sí su dialecto principal más cercano. Por ejemplo, si el usuario seleccionó español de Cuba como idioma en el dispositivo y la app no admite español de Cuba, pero sí el dialecto superior español, se usará el idioma español.

  5. Se usará el inglés si la app no admite el idioma del dispositivo.

Configura los íconos de vínculos de desviación externos

Para personalizar el ícono en el canal de vínculos de desvío externo, sube el ícono al catálogo de recursos de tu app y asegúrate de usar el mismo nombre de ícono cuando crees el vínculo de desvío externo en Configuración > Chat > Vínculos de desvío externos > Ver vínculos > Agregar vínculo de desvío en el portal de administración. Si el nombre del ícono en el portal de administración no coincide con el ícono subido a la app, el SDK usará el ícono predeterminado. Puedes consultar este vínculo para saber cómo agregar imágenes al catálogo de recursos.

Envía datos personalizados a tu CRM

Puedes enviar datos personalizados al ticket de CRM.

Existen dos métodos para enviar datos personalizados:

  1. Método seguro: firma de datos predefinida con JWT.

  2. Método no seguro: datos predefinidos con JSON sin formato (no recomendado)

Usa el método seguro para enviar datos personalizados

Debes implementar el método de firma. Primero, puedes colocar tus datos personalizados en el cliente y enviarlos a tu servidor para que los firme. En tu servidor, puedes agregar datos adicionales con un formato definido, firmarlos con tu company.secret y devolverlos por JWT.

-   (void)signPayload:(NSDictionary *)payload payloadType:(UjetPayloadType)payloadType success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure
{
    if (payloadType == UjetPayloadCustomData) {
      // sign custom data using UJET_COMPANY_SECRET on your server.

      NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
      NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];

      NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] init];
      mutableRequest.URL = [NSURL URLWithString:@"https://your.company.com/api/ujet/sign/custom_data"];
      mutableRequest.HTTPMethod = @"POST";
      NSError *error;

      // Make client's custom data
      UJETCustomData *customData = [[UJETCustomData alloc] init];
      [customData set:@"name" label:@"Name" stringValue:@"USER_NAME"];
      [customData set:@"os_version" label:@"OS Version" stringValue:[[UIDevice currentDevice] systemVersion]];
      [customData set:@"model" label:@"Model number" numberValue:[NSNumber numberWithInteger:1234]];
      [customData set:@"temperature" label:@"Temperature" numberValue:[NSNumber numberWithFloat:70.5]];
      [customData set:@"purchase_date" label:@"Purchase Date" dateValue:[NSDate date]];
      [customData set:@"dashboard_url" label:@"Dashboard" urlValue:[NSURL URLWithString:@"http://internal.dashboard.com/1234"]];

      NSDictionary *data = @{@"custom_data": [customData getData]};
      mutableRequest.HTTPBody = [NSJSONSerialization dataWithJSONObject:data options:0 error:&error];
      NSURLSessionDataTask *task = [session dataTaskWithRequest:mutableRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
          if(error) {
              failure(error);
          }
          else {
              NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
              success(json[@"jwt"]);
          }
      }];

      [task resume];
    }
}

Se usa un método no seguro para enviar datos personalizados

No se recomienda este método, ya que crea una vulnerabilidad potencial que podría exponer tu aplicación a un ataque de intermediario. Si decides usar este método, no nos hacemos responsables de la exposición de seguridad ni de los posibles daños que puedan ocurrir. Te recomendamos que uses el método seguro que se describió anteriormente para enviar datos personalizados en tu aplicación. También puedes iniciar el SDK de iOS con la instancia UJETCustomData. En este caso, el delegado signPayload para UJETPayloadCustomData solo debe llamar a success(nil);.

-   (void)signPayload:(NSDictionary *)payload payloadType:(UjetPayloadType)payloadType success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure {
    if (payloadType == UjetPayloadCustomData) {
      success(nil);
    }
}
UJETStartOptions *options = [UJETStartOptions new];
options.unsignedCustomData = customData;

[UJET startWithOptions:options];

Usa datos personalizados sin firma para enviar transcripciones de chats externos

Puedes enviar la transcripción del chat al SDK cuando se inicia con datos personalizados sin firmar. Para ello, llama al método setExternalChatTransfer: o setExternalChatTransferWithDictionary: para establecer datos JSON con NSString o NSDictionary, respectivamente.

UJETCustomData *customData = [UJETCustomData new];
[customData setExternalChatTransfer:jsonString];

UJETStartOptions *options = [UJETStartOptions new];
options.unsignedCustomData = customData;

[UJET startWithOptions:options];

Formato JSON:

  • greeting_override: string

  • agent: diccionario

    • name: string

    • avatar: cadena [URL del avatar del agente, opcional]

  • transcript: array

    • sender: string [end_user o agent]

    • timestamp: string [por ejemplo, 2021-03-15 12:00:00Z]

    • content: array

      • type: string [uno de text, media]

      • text: string [obligatorio para el tipo de texto]

      • media: diccionario [obligatorio para el tipo de medio]

        • type: cadena [image o video]

        • url: cadena [URL pública que apunta al archivo multimedia]

Ejemplo de JSON:

{
  "greeting_override": "Please hold while we connect you with a human agent.",
  "agent": {
    "name": "Name",
    "avatar": "avatar url"
  },
  "transcript": [
    {
      "sender": "agent",
      "timestamp": "2021-03-15 12:00:15Z",
      "content": [
        {
          "type": "text",
          "text": "**Suggestions shown:**\n\n* Help with batch or delivery\n* Help with metrics or order feedback\n* Help with Instant Cashout"
        }
      ]
    },
    {
      "sender": "end_user",
      "timestamp": "2021-03-15 12:00:16Z",
      "content": [
        {
          "type": "text",
          "text": "Help with batch or delivery"
        }
      ]
    }
  ]
}

Puedes usar Markdown en el tipo de texto. La sintaxis admitida incluye cursiva, negrita, lista con viñetas, hipervínculo y subrayado (--text--).

Ejemplo de datos personalizados

En esta sección, se muestra un ejemplo de datos personalizados.

JSON codificado en JWT

El archivo JSON debe validar el JWT. Y el objeto de datos personalizados es el valor de la clave custom_data.

{
  "iat" : 1537399656,
  "exp" : 1537400256,
  "custom_data" : {
    "location" : {
      "label" : "Location",
      "value" : "1000 Stockton St, San Francisco, CA, United States",
      "type" : "string"
    },
    "dashboard_url" : {
      "label" : "Dashboard URL",
      "value" : "http://(company_name)/dashboard/device_user_ID",
      "type" : "url"
    },
    "contact_date" : {
      "label" : "Contact Date",
      "value" : 1537399655992,
      "type" : "date"
    },
    "membership_number" : {
      "label" : "Membership Number",
      "value" : 62303,
      "type" : "number"
    },
    "model" : {
      "label" : "Model",
      "value" : "iPhone",
      "type" : "string"
    },
    "os_version" : {
      "label" : "OS Version",
      "value" : "12.0",
      "type" : "string"
    },
    "last_transaction_id" : {
      "label" : "Last Transaction ID",
      "value" : "243324DE-01A1-4F71-BABC-3572B77AC487",
      "type" : "string"
    },
    "battery" : {
      "label" : "Battery",
      "value" : "-100%",
      "type" : "string"
    },
    "bluetooth" : {
      "label" : "Bluetooth",
      "value" : "Bluetooth not supported",
      "type" : "string"
    },
    "wifi" : {
      "label" : "Wi-Fi",
      "value" : "Wi-Fi not connected",
      "type" : "string"
    },
    "ssn" : {
      "invisible_to_agent" : true,
      "label" : "Social Security Number",
      "value" : "102-186-1837",
      "type" : "string"
    }
  }
}

Cada dato es similar al formato de objeto JSON y debe contener la clave, el valor, el tipo y la etiqueta.

La clave es el identificador único de los datos. La etiqueta es el nombre visible en la página del CRM. El tipo es el tipo del valor.

  • cadena

    • String de JSON
  • número

    • integer, float
  • fecha

    • Formato de marca de tiempo de Unix en UTC con 13 dígitos. (contiene milisegundos)
  • URL

    • Formato de URL HTTP
Ejemplo de CRM

Este es un ejemplo de CRM.

Ubicación

Usa el framework de CoreLocation. Para obtener más información, consulta AppDelegate.m.

Versión del SO del dispositivo
[customData set:@"os_version" label:@"OS Version" stringValue:[[UIDevice currentDevice] systemVersion]];

Cómo evitar que se muestren datos personalizados

Puedes usar la propiedad invisible_to_agent con un objeto de datos personalizado para evitar que se muestren datos personalizados firmados o sin firmar en el adaptador del agente. En el ejemplo anterior, el número de seguridad social del usuario final no se muestra en el adaptador del agente porque "invisible_to_agent" : true se incluye en el objeto ssn.

Cuando incluyes la propiedad "invisible_to_agent" : true con un objeto de datos personalizado, puedes esperar el siguiente comportamiento:

Para obtener más información, consulta Visualiza los datos de la sesión en el adaptador del agente.

Propiedades de datos reservadas

Puedes enviar propiedades de datos reservados a Contact Center AI Platform (CCAI Platform) como datos personalizados firmados cuando comienza una sesión. Para obtener más información, consulta Envía propiedades de datos reservadas.

A continuación, se muestra un ejemplo de propiedades de datos reservados en los datos personalizados:

  {
    "custom_data": {
      "reserved_verified_customer": {
        "label": "Verified Customer",
        "value": false,
        "type": "boolean"
      },
      "reserved_bad_actor": {
        "label": "Bad Actor",
        "value": false,
        "type": "boolean"
      },
      "reserved_repeat_customer": {
        "label": "Repeat Customer",
        "value": false,
        "type": "boolean"
      }
    }
  }
  

Establece cada valor en true o false.

  • reserved_verified_customer: true si consideras que este usuario final es un cliente legítimo.
  • reserved_bad_actor: true si consideras que este usuario final podría ser un actor de mala fe.
  • reserved_repeat_customer: true si determinaste que este usuario final ya se comunicó con tu centro de contacto.

Personaliza Flow

En esta sección, se describe cómo personalizar el flujo.

Desconecta CCAI Platform para controlar los eventos de la app de host

// CCAI Platform is connected
...
// An event has come
[UJET disconnect:^{
  // Handle an event
}];

Posponer una llamada o un chat entrantes de CCAI Platform

Implementa un método delegado para controlar los eventos entrantes:

-   (BOOL)shouldConnectUjetIncoming:(NSString *)identifier forType:(UjetIncomingType)type {
  if (weDoingSomething) {
    // save identifier and type
    return NO; // postpone
  } else {
    return YES;
  }
}
Conectar evento pospuesto
[UJET connect:identifier forType:UjetIncomingTypeCall];

Configura el vínculo directo

Esto permite que los agentes en llamadas de RTC usen acciones inteligentes por SMS, tanto cuando un usuario final tiene la app como cuando no la tiene.

Ve a Configuración > Administración de operaciones > Habilitar el envío de SMS para descargar la app en el portal de CCAI Platform.

Puedes establecer la URL de la app con tu página web (es decir, https://your-company.com/support) después de configurar el vínculo universal o el esquema URL personalizado. Puedes seleccionar cualquiera de las dos opciones.

Implementa el método delegado para controlar el vínculo directo

El vínculo universal y la URL personalizada se ven como https://your-company.com/support?call_id=x&nonce=y y your-company://support?call_id=x&nonce=y, respectivamente. Coloca uno de tus vínculos sin parámetros de consulta en URL de la app en el portal de administración. Por ejemplo, coloca your-company://support si usas un esquema de URL personalizado.

En el método delegado, asegúrate de llamar a [UJET start] solo cuando las rutas de URL y los parámetros del vínculo universal o la URL personalizada sean específicos de CCAI Platform.

-   (BOOL)application:(UIApplication *)app continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler {
  ...
  if ([NSUserActivityTypeBrowsingWeb isEqualToString:userActivity.activityType]) {
    NSURL *url = userActivity.webpageURL;

    NSArray *availableSchema = @[
                                  @"your-company",   // custom URL scheme
                                  @"https"           // universal link
                                  ];

    NSArray *availableHostAndPath = @[
                                      @"ujet",                  // custom URL scheme
                                      @"your-comany.com/ujet"   // universal link
                                      ];

    if (![availableSchema containsObject:url.scheme]) {
      return NO;
    }

    NSString *hostAndPath = [NSString stringWithFormat:@"%@%@", url.host, url.path];
    if (![availableHostAndPath containsObject:hostAndPath]) {
      return NO;
    }

    // your-company://ujet?call_id={call_id}&nonce={nonce}
    // https://your-company.com/ujet?call_id={call_id}&nonce={nonce}
    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url
                                                resolvingAgainstBaseURL:NO];
    NSArray *queryItems = urlComponents.queryItems;
    NSString *callId = [self valueForKey:@"call_id" fromQueryItems:queryItems];
    // validate call ID
    if (![self isValidCallId:callId]) {
      return NO;
    }
    NSString *nonce = [self valueForKey:@"nonce" fromQueryItems:queryItems];

    UJETStartOptions *options = [[UJETStartOptions alloc] initWithCallId:callId nonce:nonce];

    [UJET startWithOptions:options];
  }
  ...
}

Si tu app adopta UIWindowSceneDelegate, agrega este fragmento de código:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        //if app is called with universal Link and started from cold
        if connectionOptions.urlContexts.first != nil  {
            self.scene(scene, openURLContexts: connectionOptions.urlContexts)
        }

        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func scene(_ scene: UIScene, willContinueUserActivityWithType userActivityType: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let _ = appDelegate.application(UIApplication.shared,
                                        continue: NSUserActivity(activityType: userActivityType)) { _ in

        }
    }

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url else {
            return
        }

        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let _ = appDelegate.application(UIApplication.shared,
                                        open: url,
                                        options: [:])
    }
}

Para obtener más información, consulta los códigos de ejemplo del archivo UJETObject+DeepLink.

Observa el evento de CCAI Platform

Publicamos los siguientes eventos a través de NSNotificationCenter.defaultCenter. Puedes escucharlos y personalizar tu flujo según tu caso de uso, p.ej., el diseño de teclado personalizado.

  • UJETEventEmailDidClick

    • Datos del menú de la fila
  • UJETEventEmailDidSubmit

    • Datos del menú de la fila

    • has_attachment: (NSNumber) @YES, @NO

  • UJETEventSessionViewDidAppear

    • type: @"call", @"chat"

    • timestamp: (NSString) ISO 8601

  • UJETEventSessionViewDidDisappear

    • type: @"call", @"chat"

    • timestamp: (NSString) ISO 8601

  • UJETEventSessionDidCreate

    • Datos de la sesión
  • UJETEventSessionDidEnd

    • Datos de la sesión

    • agent_name: (NSString) nulo si el agente no se unió

    • duration: (NSNumber) solo para llamadas

    • ended_by: (NSString)

      • type=call: @"agent", @"end_user"

      • type=chat: @"agent", @"end_user", @"timeout", @"dismissed"

  • UJETEventSdkDidTerminate

  • UJETEventPostSessionOptInDidSelected

    • opt_in_selected: (NSString) @"Yes", @"No"

Datos de eventos

Metadatos

  • application: @"iOS"

  • app_id: Identificador de paquete (NSString)

  • app_version: (NSString)

  • company: Subdominio (NSString)

  • device_model: (NSString)

  • device_version: (NSString)

  • sdk_version: (NSString)

  • timestamp: (NSString) ISO 8601

Datos del menú de colas

Metadatos

  • menu_id: NSString

  • menu_key: NSString, nullable

  • menu_name: NSString

  • menu_path : NSString

Datos de la sesión

Metadatos

  • session_id: NSString

  • type: @"call", @"chat"

  • end_user_identifier: NSString

Configura el uso compartido de pantalla

Si quieres usar la función de compartir pantalla, integra UJETCobrowseKit.xcframework.

CocoaPods

Agrega esta subespecificación al destino de tu app:

    ruby
target 'MyApp' do
  pod 'UJET'
  pod 'UJET/Cobrowse'
end

Configura el uso compartido de pantalla con Carthage

Agrega esta línea en el Cartfile:

binary "https://sdk.ujet.co/ios/UJETKit.json"

SwiftPM

Selecciona los productos UJET y UJETCobrowse, y agrégalos al destino de tu app.

Y establece el valor de UJETGlobalOptions.cobrowseKey en la clave de navegación conjunta de tu empresa.

swift
let options = UJETGlobalOptions()
options.cobrowseKey = cobrowseKey

UJET.setGlobalOptions(options)

Compartir pantalla completa del dispositivo (opcional)

La función para compartir pantalla completa del dispositivo permite que los agentes de asistencia vean las pantallas de aplicaciones externas a la tuya. Esto suele ser útil cuando los agentes de asistencia necesitan verificar el estado de la configuración del sistema o ver al usuario navegar entre varias aplicaciones. Si no quieres esta función, puedes omitir esta sección.

Para personalizar el cuadro de diálogo de consentimiento de uso compartido de pantalla, debes implementar el protocolo UJETCobrowseAlertProvider en tu clase de proveedor. En esta implementación, devuelve un UIViewController personalizado o cualquier otro objeto heredado UIViewController a través del método de protocolo correspondiente. UIViewController debe tener dos botones: uno para aceptar y otro para rechazar.

Después de obtener el consentimiento, pásalo a nuestro SDK llamando al cierre consentStatus. UIViewController de cobrowseFullDeviceRequestAlert. El delegado debe contener el RPSystemBroadcastPickerView con el título (consulta el código de muestra que aparece a continuación) y debe tener otro botón de rechazo. Invoca el cierre descartado cuando se haga clic en el botón de rechazo.

class CobrowseAlertProvider: NSObject, UJETCobrowseAlertProvider {
    func cobrowseSessionInitializationAlert(consentStatus: @escaping (Bool) -> Void) -> UIViewController? {
        let customAlertViewController = CustomAlertViewController()
        customAlertViewController.consentStatus = consentStatus
        return customAlertViewController
    }

    func cobrowseSessionRequestAlert(consentStatus: @escaping (Bool) -> Void) -> UIViewController? {
        // Same as cobrowseSessionInitializationAlert
    }

    func cobrowseRemoteRequestAlert(consentStatus: @escaping (Bool) -> Void) -> UIViewController? {
        // Same as cobrowseSessionInitializationAlert
    }

    func cobrowseFullDeviceRequestAlert(dismissed: @escaping () -> Void) -> UIViewController? {
        let customAlertViewController = CustomFullDeviceAlertViewController()
        cobrowseSessionAlertViewController.dismissed = dismissed
        return customAlertViewController
    }

    func cobrowseSessionEndAlert(consentStatus: @escaping (Bool) -> Void) -> UIViewController? {
        // Same as cobrowseSessionInitializationAlert
    }
}

El controlador de vista personalizado debe tener un cierre para pasar el estado de consentimiento al SDK.

class CustomAlertViewController: UIViewController {
    var consentStatus: ((Bool) -> Void)?

    @IBAction func allowButtonClicked(_ sender: Any) {
        dismiss(animated: true) {[weak self] in
            self?.consentStatus?(true)
        }
    }

    @IBAction func denyButtonClicked(_ sender: Any) {
        dismiss(animated: true) {[weak self] in
            self?.consentStatus?(false)
        }
    }
}

El controlador de vista personalizado para la alerta de solicitud de dispositivo completo debe tener RPSystemBroadcastPickerView y un cierre para pasar el estado de dismiss al SDK.

class CustomFullDeviceAlertViewController: UIViewController {
    var broadcastPickerView: RPSystemBroadcastPickerView!
    var dismissed: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()

        let frame = CGRect(x: x, y: y, width: 50, height: 50) // Set your own value
        broadcastPickerView = RPSystemBroadcastPickerView(frame: frame)
        broadcastPickerView.preferredExtension = Bundle.main.object(forInfoDictionaryKey: "CBIOBroadcastExtension") as? String // Should have this value as it is
        view.addSubview(broadcastPickerView)
    }

    @IBAction func denyButtonClicked(_ sender: Any) {
        dismiss(animated: true) {[weak self] in
            self?.dismissed?()
        }
    }
}

No olvides pasar este proveedor a nuestro SDK a través de la siguiente API:

let provider = CobrowseAlertProvider()
UJET.setCobrowseAlertProvider(provider)

Extensión de transmisión

La función requiere que se agregue una extensión de transmisión.

  1. Abre tu proyecto de Xcode.

  2. Navega a File > Target.

  3. Elige Broadcast Upload Extension.

  4. Ingresa un Nombre para el destino.

  5. Desmarca la casilla de verificación Include UI Extension.

  6. Crea el destino y toma nota de su ID del paquete.

  7. Cambia el SDK de destino de tu extensión de transmisión a iOS 12.0 o una versión posterior.

Integra el SDK.

CocoaPods{: Agrega la siguiente subespecificación al destino de tu extensión:

target 'MyApp' do
  pod 'UJET'
  pod 'UJET/Cobrowse'
end
target 'MyAppExtension' do
  pod 'UJET/CobrowseExtension'
end

Si usas SwiftPM, selecciona el producto UJETCobrowseExtension y agrégalo al destino de tu extensión.

Cómo configurar el uso compartido de llaveros

Tu app y la extensión de la app que creaste anteriormente deben compartir algunos secretos a través del llavero de iOS. Para ello, usan su propio grupo de llaveros, de modo que están aislados del resto del llavero de tus apps.

En Xcode, selecciona el destino de tu app y el de tu extensión. Agrega un derecho de uso compartido de llavero para el grupo de llaveros io.cobrowse.

Agrega el ID del paquete a tu archivo plist

Toma el ID del paquete de la extensión que creaste anteriormente y agrega esta entrada en el archivo Info.plist de tu app.

xml
<key>CBIOBroadcastExtension</key>
<string>your.app.extension.bundle.ID.here</string>

Implementa la extensión

Xcode habrá agregado los archivos SampleHandler.m y SampleHandler.h (o SampleHandler.swift) como parte del destino que creaste antes. Reemplaza el contenido de los archivos por lo siguiente:

Swift:

Selecciona el producto UJETCobrowseExtension y agrégalo a tu segmentación por extensión:

import CobrowseIOAppExtension
class SampleHandler: CobrowseIOReplayKitExtension {
}

ObjC:

objc// SampleHandler.h
@import CobrowseIOAppExtension;
@interface SampleHandler : CobrowseIOReplayKitExtension
@end// SampleHandler.m
#import "SampleHandler.h"
@implementation SampleHandler
@end

Compila y ejecuta tu app

Ya puedes compilar y ejecutar tu app. La capacidad completa del dispositivo solo está disponible en dispositivos físicos, no funcionará en el simulador de iOS.

Minimiza el SDK

El SDK de Contact Center AI Platform se puede minimizar cuando hay una sesión de chat o una llamada en curso. Esto puede ser útil cuando quieras dirigir al usuario de vuelta a tu app después de que se reciba un evento del SDK, como un clic en una tarjeta de contenido. Para minimizar el SDK y dirigir al usuario de vuelta a tu app, puedes usar lo siguiente:

UJET.minimize(nil)
 // Or if you want to take some action once the SDK has been minimized:

UJET.minimize {
  // Add the code you want to run once the SDK has been minimized here
}

Resguardo

Puedes usar la función didHandleUjetError para el resguardo de errores inesperados. Si no usas esta función o si devuelve false, el SDK de iOS controla el error.

En la siguiente tabla, se muestran los errores que escucha la función didHandleUjetError:

Tipo de error Código de error Descripción
networkError 1 La red no está disponible.

Nota: Este error no se activa cuando la red no está disponible durante una sesión de chat o llamada, o una pantalla de calificación.

authenticationError 100 Se produjo un error inesperado durante la autenticación.
authenticationJwtError 101 Se produjo un error inesperado durante la validación del JWT, por ejemplo, un error de análisis.
voipConnectionError 1000 No se pudo establecer una conexión con el proveedor de VoIP. Una devolución de llamada del SDK de VoIP controla esto.
voipLibraryNotFound 1001 El sistema espera que una llamada se conecte a través de un proveedor de VoIP, pero no puede encontrar uno. Esto puede suceder si integras el SDK incorrecto o no agregas una biblioteca de proveedor de VoIP a tus dependencias.
chatLibraryNotFound 1100 Se produce cuando el sistema no puede encontrar la biblioteca de chat. Esto puede suceder cuando integras el SDK incorrecto o no agregaste una biblioteca de chat de Twilio a tus dependencias.

En la siguiente muestra de código, se indica cómo usar la función didHandleUjetError:

public func didHandleUjetError(_ errorCode: Int32) -> Bool {
    guard let ujetError = UjetErrorCode(rawValue: Int(errorCode)) else {
        return false // Let the SDK handle unknown integer codes.
    }

    switch ujetError {
    case .networkError:
        // Example for if you have a custom UI for network errors. You can
        // handle the error and prevent the SDK from showing its own alert.
        showCustomNetworkAlert() // Your custom UI for this type of error.
        return true

    case .authenticationError, .voipConnectionError:
        // For all other errors, use the default SDK behavior.
        return false

    @unknown default:
        // Let the SDK handle future errors.
        return false
    }
}

Soluciona problemas

En esta sección, se proporciona información para ayudarte a solucionar problemas habituales.

Rechazo del envío de la app

Se rechazó el envío de la app debido a la inclusión del framework de CallKit en el territorio de China.

Si Apple rechaza tu app por este motivo, deja un comentario, ya que el sistema está diseñado para desactivar el framework de CallKit para la región de China en las llamadas de VoIP. Esto entra en vigencia a partir de la versión 0.31.1 del SDK.

El tamaño del SDK es demasiado grande

Cuando el tamaño del SDK es demasiado grande y difícil de seguir en GitHub

En este artículo, ofrecen dos opciones. Se recomienda usar Git LFS.

Si no usas Bitcode, quitar el código de bits del objeto binario puede ser otra opción. Ejecuta este comando en la carpeta UJETKit.xcframework.

xcrun bitcode_strip -r UJET -o UJET

Error dyld: Library not loaded

Agrega @executable_path/Frameworks en Runpath Search Paths desde Target > Build Settings > Linking.

Envío de la app en iTunes Connect

Apple podría hacer la siguiente pregunta durante el proceso de revisión debido al modo en segundo plano de voz por IP habilitado:

¿Los usuarios pueden recibir llamadas de VoIP en tu app?

Responde Yes a la pregunta.

La notificación de alerta no está disponible cuando se inicia el SDK.

Verifica lo siguiente:

  • Usa un dispositivo real, no un simulador.

  • Habilita las notificaciones push y la función de voz por IP en Background Modes > Voice over IP capability.

Si eso no funciona, intenta compilar con el perfil de aprovisionamiento de distribución (Ad-hoc o Apple Store).

Prueba la notificación push en tu app de prueba

Prepara tu certificado de VoIP y el token del dispositivo.

En el portal de CCAI Platform, consulta la sección Depuración de notificaciones push en el menú Configuración > Configuración para desarrolladores.

Si ya configuraste el certificado para APNS, no es necesario que lo vuelvas a ingresar.

Ingresa tu certificado (opcional), marca si es una zona de pruebas o no (opcional) y, luego, ingresa el token de dispositivo para notificaciones push de tu app de prueba.

Iniciar un nuevo chat tardó más de 30 segundos

Verifica si estás respondiendo a un método delegado de datos personalizados. Debes devolver datos personalizados válidos cuando se soliciten o simplemente devolver nil en el bloque de éxito.

Usa este fragmento de código como un ejemplo de configuración:

public func signPayload(_ payload: [AnyHashable: Any]?, payloadType: UjetPayloadType, success: (String?) -> Void, failure: (Error?) -> Void)
{
    if payloadType == .customData {
        success(nil)
    }
}