El SDK para dispositivos móviles de Contact Center AI Platform (CCAI Platform) para el sistema operativo iOS de Apple permite incorporar la experiencia para dispositivos móviles de CCAI Platform en aplicaciones para dispositivos móviles de iOS.
Requisitos
El SDK para iOS tiene los siguientes requisitos:
- iOS 12.0 y versiones posteriores
Recupera las credenciales de la empresa
Accede al portal de Contact Center AI Platform (CCAI Platform) con credenciales de administrador.
Ve a Configuración > Configuración para desarrolladores.
En Clave de la empresa y Código secreto, anota la clave 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 iOS de la plataforma de CCAI.
Instalación
Para comenzar, debes instalar el SDK de iOS.
Descarga la app de ejemplo
Descarga la app de ejemplo para iOS.
Navega a la carpeta y, luego, instala las dependencias con CocoaPods:
$ pod install --project-directory=ExampleAppPara configurar rápidamente los parámetros del proyecto, ejecuta una secuencia de comandos de shell:
$ ./setup.shComo alternativa, puedes editar manualmente la configuración del proyecto siguiendo estos pasos:
Abre
ExampleApp.xcworkspace.Reemplaza los valores
UJETCompanyKeyyUJETCompanySecretenInfo.plistpor los valores de Company Key y Company Secret Code de la página Settings > Developer Settings en el portal de CCAI Platform.Reemplaza el valor de
UJETSubdomainenInfo.plistpor el subdominio de la URL de tu portal de CCAI Platform. El subdominio precede directamente a.ujet.comen la URL, por ejemplo,your-subdomainenhttps://your-subdomain.ujet.com/settings/developer-setting.
Integración en tu proyecto
La integración del SDK para iOS con tu aplicación depende de tu entorno de desarrollo.
Administrador de paquetes de Swift
Agrega el paquete de Swift para el SDK de iOS.
En la configuración de compilación, coloca -ObjC en Other Linker Flags.
En la versión más reciente de Xcode (actualmente, 13.2), hay 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 Run Script a las fases de compilación de tu proyecto de Xcode. Esta fase de Run Script debe aparecer después de la fase de compilación de Embed Frameworks. Esta nueva fase Run Script 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
Agrega la siguiente línea al Podfile:
pod 'UJET', :podspec => 'https://sdk.ujet.co/ios/x.y.z/ujet.podspec' #specific version x.y.zEjecuta pod install. Si ya se integró el SDK de iOS, ejecuta pod update CCAI Platform.
Carthage
Google Cloud recomienda usar un administrador de dependencias o una integración manual, ya que una dependencia de la CCAI Platform no admite Carthage. Para ello, agrega las siguientes líneas:
binary "https://sdk.ujet.co/ios/UJETKit.json
binary "https://sdk.ujet.co/ios/UJETFoundationKit.json
binary https://raw.githubusercontent.com/twilio/twilio-voice-ios/Releases/twilio-voice-ios.json
Integración manual
No se admite: https://github.com/twilio/conversations-ios/issues/12.
binario https://raw.githubusercontent.com/twilio/conversations-ios/master/twilio-convo-ios.json
Ejecuta
carthage bootstrap --use-xcframeworks(ocarthage update --use-xcframeworkssi actualizas las dependencias).Descarga
UJETKit.xcframework,UJETFoundationKit.xcframework,UJETChatRedKit.xcframework,UJETChatBlueKit.xcframework,UJETTwilioCallKit.xcframeworky todas las dependenciasTwilioVoice.xcframeworkyTwilioConversationsClient.xcframework.Agrega UJETKit.xcframework a tu destino arrastrándolo a la sección Frameworks, Libraries, and Embedded Content.
Repite los pasos 2 y 3 en todas las dependencias del paso 1.
En Build Settings, coloca
-ObjCenOther Linker Flags.Agrega
libc++.tbdcomo una dependencia en la secciónLinked Frameworksdel 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:
Descarga todos los frameworks, incluidos
UJETKit.xcframeworky otras dependencias.Crea la carpeta CCAI Platform en la raíz del proyecto y extrae todos los frameworks.
Selecciona el destino
Objc-ManualoSwift-Manualy compila.
Importar 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 correctamente, la aplicación intercambia el JWT firmado por el token de autenticación del usuario final. Se debe llamar al bloque de éxito o falla antes de que devuelva el delegado.
Para el usuario anónimo (identificador = nil), la aplicación creará un UUID para el usuario. Si, en un momento posterior, el usuario se autentica con un identificador, la aplicación intentará combinar los dos usuarios según el UUID.
En UJETObject.h del proyecto de ejemplo, haz lo siguiente:
@import UJETKit;
@interface UJETObject : NSObject <UJETDelegate>
Implementa signPayload: payloadType: success: failure: método delegado.
- (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 aplicaciones, no desde 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 aplicación envía notificaciones push para solicitar Smart Actions, como la verificación y la foto, además de informar sobre una llamada entrante. La aplicación requiere que se guarden dos tipos diferentes de certificados (VoIP y APNs) en el portal del administrador.
Prepara el certificado de servicios de VoIP
La documentación de referencia está disponible para la notificación push de VoIP de Apple.
Crea y descarga el certificado de VoIP desde el sitio para desarrolladores de Apple.
Haz doble clic en el certificado para agregarlo al Llavero.
Inicia la aplicación Acceso a Llavero en tu Mac.
Elige la categoría Mis certificados en la barra lateral izquierda.
Haz clic con el botón derecho en el certificado de VoIP Services: your.app.id.
En el menú emergente, elige Exportar.
Guárdalo como cert.p12 sin protegerlo con una contraseña. Para ello, deja el campo de contraseña en blanco.
Ejecuta el siguiente comando en la terminal.
openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -debug -showcertLa parte superior de cert.pem es el certificado y la parte inferior es la clave privada.
Verifica que tu certificado funcione con el servidor de notificaciones push de Apple.
openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -debug -showcertsSi se ejecuta correctamente, debería mostrar lo siguiente:
--- New, TLSv1/SSLv3, Cipher is AES256-SHA Server public key is 2048 bit Secure Renegotiation IS supported Compression: NONE Expansion: NONE SSL-Session: Protocol : TLSv1 Cipher : AES256-SHA Session-ID: Session-ID-ctx: Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Key-Arg : None Start Time: 1475785489 Timeout : 300 (sec) Verify return code: 0 (ok) ---Accede al portal de la Plataforma de CCAI con credenciales de administrador y ve a Configuración > Configuración para desarrolladores > App para dispositivos móviles.
Completa el certificado en la sección "Certificado de servicios de VoIP" y guárdalo. Asegúrate de incluir límites (
-----BEGIN-----y-----END-----) para el certificado y la clave privada.Marca la casilla de verificación Sandbox si ejecutas una app con un perfil de aprovisionamiento de desarrollo, como la depuración en Xcode. Si tu app se archivó para Ad hoc o App Store y usa un perfil de aprovisionamiento de distribución, desmarca la casilla de verificación Sandbox.
Prepara el certificado SSL del servicio de notificaciones push de Apple
El proceso para esto es similar al de los certificados de servicio de VoIP. En este caso, se usa el certificado SSL del servicio de notificaciones push de Apple (zona de pruebas y producción). Puedes consultar la documentación del servidor de notificaciones remotas de Apple para obtener orientación sobre cómo crear el certificado.
Integración de notificaciones push
En AppDelegate.m:
@import PushKit;
@interface AppDelegate() <PKPushRegistryDelegate>
In application:didFinishLaunchingWithOptions: method:
// Initialize CCAI Platform
[UJET] initialize:UJET_COMPANY_KEY subdomain:UJET_SUBDOMAIN delegate:self];
// Register for VoIP notifications on launch.
PKPushRegistry *voipRegistry = [[PKPushRegistry alloc] initWithQueue: dispatch_get_main_queue()];
voipRegistry.delegate = self;
voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
Agrega los siguientes métodos de delegado en el archivo de implementación del protocolo UIApplicationDelegate:
Imprime tu token del dispositivo para probar las notificaciones push.
// PKPushRegistryDelegate
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type {
[UJET updatePushToken:credentials.token type:UjetPushTypeVoIP];
}
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
if (payload.dictionaryPayload[@"ujet"]) {
[UjetreceivedNotification:payload.dictionaryPayload completion:completion];
} else {
completion();
}
}
// UIApplicationDelegate
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[UjetupdatePushToken:deviceToken type:UjetPushTypeAPN];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
if (userInfo[@"ujet"]) {
[UJET receivedNotification:userInfo completion:nil];
}
}
// UserNotificationsDelegate overrides [UIApplicationDelegate didReceiveRemoteNotification:]
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
NSDictionary *userInfo = notification.request.content.userInfo;
if (userInfo[@"ujet"] != nil) {
[UJET receivedNotification:userInfo completion:nil];
}
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
NSDictionary *userInfo = response.notification.request.content.userInfo;
if (userInfo[@"ujet"] != nil) {
[UJET receivedNotification:userInfo completion:nil];
}
}
Habilita las notificaciones push
Selecciona tu destino y abre la pestaña Capabilities.
Activa el interruptor de Notificaciones push.
Prueba las notificaciones push
En las siguientes secciones, se proporciona orientación para probar las notificaciones push.
Sección de depuración de notificaciones push
En el portal del administrador, navega a Configuración > Configuración para desarrolladores. En esta página, busca la sección titulada Push Notification Debug:

Copia y pega el token del dispositivo en el área de texto correcta y selecciona la app para dispositivos móviles correcta.
Obtén el token del dispositivo
Un ejemplo de cadena de token de dispositivo se ve de la siguiente manera:
7db0bc0044c8a203ed87cdab86a597a2c43bf16d82dae70e8d560e88253364b7
Por lo general, las notificaciones push se configuran en la clase que se ajusta al protocolo UIApplicationDelegate o PKPushRegistryDelegate. En algún momento, el token del dispositivo estará disponible para ti. Puedes imprimirlo antes de pasarlo al SDK para iOS. Para obtener el token del 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 Push notification successfully configured si la notificación push de prueba se entregó correctamente.
No se garantiza la entrega de notificaciones push al 100%, ya que depende de la conexión de red del dispositivo.
Configuraciones 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 de aplicación
Modos en segundo plano (verifica 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 su 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, se cerrará.
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 que el cliente acceda a la cámara para tomar y enviar fotos relacionadas con su consulta de asistencia al cliente.
NSPhotoLibraryUsageDescription: Permite que el cliente envíe 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 a través de 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 (PAD). En los siguientes pasos, se indica cómo crear una DAP:
Accede al portal de CCAI Platform con credenciales de administrador.
Ve a Configuración > Cola.
Selecciona cualquier fila de la estructura del menú.
Selecciona Create direct access point.
Ingresa la clave en el formulario de texto.
Haz clic en Guardar.

Borra la caché local si se actualizaron los datos del usuario
Almacenamos en caché el token de autorización en el llavero para reutilizarlo y realizar solicitudes menos frecuentes para firmar la carga útil desde la app del host. El SDK lo usará hasta que venza o se revoque a través de la llamada clearUserData. La app host es la encargada de revocar esta caché cada vez que se actualizan o cambian los datos relacionados con el usuario, como un evento de cierre de sesión.
[UJET clearUserData];
Verifica si hay una sesión existente antes de iniciar Contact Center AI Platform
Antes de iniciar una sesión, verifica si no hay una sesión actual. Esto es especialmente importante cuando cambió 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
En UJETGlobalTheme.h, se enumeran varias opciones para el tema del SDK.
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 > Display Name.
Puedes establecer 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 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 tono del color que desees para el modo oscuro y 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 establezcas esta propiedad si tu app admite el modo oscuro. Consulta los siguientes artículos de Apple para elegir el color de tinte adecuado para el modo oscuro:
Tema del chat
Para personalizar la pantalla de chat, puedes usar una cadena JSON o cada clase de tema.
Para obtener más información, consulta la app de ejemplo y quita la marca de 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 personalización para 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 de formulario
Puedes agregar personalización para las tarjetas de formularios 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 capacidad del 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:
Extrae la información pertinente del diccionario de eventos (
smart_action_id,external_form_idysignature).Genera un URI del formulario y una firma para los datos del formulario.
Pasa los datos del formulario al SDK como un diccionario
FormDataEventconcompletion closure.Si se produce algún error durante la generación de la URI o la firma, invoca la devolución de llamada con
callback.onError()y elError.
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-SHA256) se debe generar con data y firmar con la clave secreta compartida. Las claves de objeto de los datos deben ordenarse alfabéticamente antes de generar firmas, y el mismo data debe enviarse al SDK.
Transferencia posterior a la sesión
Puedes agregar personalización para la 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 deseas diferenciar la experiencia del VA 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 de chat
Puedes agregar personalización para el menú de acciones del 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 tu Xcassets llamada "icon-call-kit".
Configura el SDK
Puedes establecer 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];
Cómo mostrar u ocultar el botón para descargar la transcripción
Puedes configurar el SDK para mostrar u ocultar el botón de descarga de la transcripción en el menú de opciones del chat y en la pantalla posterior al chat.
El siguiente código muestra cómo configurar el botón de descarga de la transcripción:
typedef NS_OPTIONS(NSUInteger, UJETChatDownloadTranscriptVisibilityOptions) {
UJETChatDownloadTranscriptVisibilityOptionsShowAll = 0,
UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu = 1 << 0,
UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen = 1 << 1,
UJETChatDownloadTranscriptVisibilityOptionsHideAll = UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu | UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen
};
@property (nonatomic, assign) UJETChatDownloadTranscriptVisibilityOptions transcriptVisibilityOptions;
Respaldo de PSTN
Proporcionamos una alternativa de PSTN para varias situaciones:
La red móvil está sin conexión.
No se puede acceder al backend de la aplicación.
VoIP no está disponible
La condición de la red no es lo suficientemente buena 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 el signo + seguido del código de país y el número de teléfono, p. ej., +18001112222.
Sensibilidad de resguardo de la RTC
Puedes ajustar el nivel de sensibilidad de la verificación de la condición de la red para la conmutación por recuperación a la RTCP.
@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 RTPC en lugar de VoIP. La latencia máxima y el umbral de ancho de banda mínimo son de 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:
Accede al portal de CCAI Platform como administrador.
Ve a Configuración > Configuración para desarrolladores > Apps para dispositivos móviles.
Busca la sección Umbral de número de teléfono de resguardo. El valor predeterminado es 0.85.
Especifica el nuevo valor del umbral.
Haz clic en Guardar.
Cómo desactivar las notificaciones push a nivel global
Puedes desactivar las notificaciones push a nivel global. Si se configura la siguiente propiedad en 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 la plataforma de CCAI específicamente con esta propiedad:
@property (nonatomic, assign) BOOL ignoreDarkMode;
Ocultar la barra de estado
Puedes controlar la visibilidad de la barra de estado con esta propiedad:
@property (nonatomic, assign) BOOL hideStatusBar;
De forma predeterminada, hideStatusBar se establece en false y visible.
Omitir la encuesta de CSAT
Puedes agregar un botón que le permita al usuario omitir la encuesta de CSAT. En el siguiente ejemplo de código, se muestra 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 cargador (dentro de un UIView) al SDK y anular el UIActivityIndicatorView predeterminado. Implementa el método ujet_activityIndicator desde 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.
Idioma seleccionado en la pantalla de presentación de la app.
Se seleccionó el idioma predeterminado de
UJETGlobalOptions. Puedes establecer el idioma predeterminado con la propiedadpreferredLanguage. Los códigos de idioma admitidos se pueden encontrar en el archivoUJETGlobalOptions.h.Se usará el idioma del dispositivo seleccionado en el dispositivo (desde Configuración > General > Idioma y región) cuando la app lo admita.
Se usará el dialecto más cercano al idioma del dispositivo cuando la aplicación no admita el idioma del dispositivo, pero sí admita su dialecto principal más cercano. Por ejemplo, si el usuario seleccionó español (Cuba) como idioma en el dispositivo y la app no admite español (Cuba), pero sí admite el dialecto principal español, se usará el idioma español.
Se usará el inglés si la app no admite el idioma del dispositivo.
Configura los íconos de vínculos de desviación externos
Puedes personalizar el ícono en el canal de vínculos de redireccionamiento externo. Para ello, 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 redireccionamiento externo en Configuración > Chat > Vínculos de redireccionamiento externo > Ver vínculos > Agregar vínculo de redireccionamiento en el Portal del administrador. 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 sobre cómo agregar imágenes al catálogo de recursos.



Resguardo
Puedes usar la función didHandleUjetError para la resiliencia ante errores inesperados. Si no usas esta función o si muestra false, el SDK para iOS controla el error.
En la siguiente tabla, se muestran los errores que detecta 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 en 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 la llamada se conecte a través de un proveedor de VoIP, pero no puede encontrar uno. Esto puede ocurrir si integras el SDK incorrecto o no agregas una biblioteca de proveedores de VoIP a tus dependencias. |
chatLibraryNotFound |
1100 | Se produce cuando el sistema no puede encontrar la biblioteca de chat. Esto puede ocurrir 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 muestra 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
}
}
Envía datos personalizados a tu CRM
Puedes enviar datos personalizados al ticket de CRM.
Existen dos métodos para enviar datos personalizados:
Método seguro: Firma de datos predefinida con JWT.
Método no seguro: Datos predefinidos con JSON sin formato (no recomendado).
Cómo usar 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 firmarlos. En tu servidor, puedes agregar datos adicionales con un formulario definido y firmar con tu company.secret, y devolverlos con 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];
}
}
Usa un método no seguro para enviar datos personalizados
No se recomienda este método, ya que crea una posible vulnerabilidad que podría exponer tu aplicación a un ataque de intermediario. Si eliges usar este método, no nos hacemos responsables de la exposición a la 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 para 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];
Cómo usar datos personalizados sin firmar para enviar transcripciones de chat externas
Puedes enviar la transcripción del chat al SDK cuando se inicia con datos personalizados sin firmar llamando 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: dictionary
name: string
avatar: cadena [URL del avatar del agente, opcional]
transcripción: array
sender: string ["end_user" or "agent"]
timestamp: string [es decir, "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: string [one of image, video]
url: string [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, listas con viñetas, hipervínculos y subrayado (--text--).
Ejemplo de datos personalizados { :#example-of-custom-data }
JSON codificado en JWT
El archivo JSON debe validar el JWT. 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 y el tipo es el tipo de valor.
cadena
- String de JSON
número
- número entero, número de punto flotante
fecha
- Es el formato de marca de tiempo de Unix en UTC con 13 dígitos. (contiene milisegundos)
URL
- Formato de URL HTTP
Ejemplo de CRM

Ubicación
Usar 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:
- Los datos personalizados se incluyen en el archivo de metadatos de la sesión.
- Los datos personalizados no se incluyen en los registros del CRM.
Para obtener más información, consulta Cómo ver los datos de la sesión en el adaptador del agente.
Propiedades de datos reservadas
Puedes enviar propiedades de datos reservadas 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 las propiedades de datos reservadas en los datos personalizados:
{
"custom_data": {
"reserved_verified_customer": {
"label": "Verified Customer",
"value": "VERIFIED_CUSTOMER_BOOLEAN": ,
"type": "boolean"
},
"reserved_bad_actor": {
"label": "Bad Actor",
"value": "VERIFIED_BAD_ACTOR_BOOLEAN": ,
"type": "boolean"
},
"reserved_repeat_customer": {
"label": "Repeat Customer",
"value": "REPEAT_CUSTOMER_BOOLEAN": ,
"type": "boolean"
}
}
}
Reemplaza lo siguiente:
VERIFIED_CUSTOMER_BOOLEAN: Es verdadero si consideras que este usuario final es un cliente legítimo.VERIFIED_BAD_ACTOR_BOOLEAN: Es verdadero si consideras que este usuario final podría ser una persona que actúa de mala fe.REPEAT_CUSTOMER_BOOLEAN: Es verdadero si determinaste que este usuario final se comunicó con tu centro de contacto anteriormente.
Personalizar 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 la llamada o el chat entrante de CCAI Platform
Implementa un método de 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 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 > Habilita la opción Enviar SMS para descargar la app en el portal de la plataforma de CCAI.
Puedes configurar 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 de URL personalizado. Puedes seleccionar cualquiera de las dos opciones.
Implementa el método de 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 búsqueda en la URL de la app en el portal del administrador. Por ejemplo, ingresa your-company://support si usas un esquema de URL personalizado.
En el método de delegado, asegúrate de llamar a [UJET start] solo cuando las rutas y los parámetros de URL en el vínculo universal o la URL personalizada sean específicos para la Plataforma de CCAI.
- (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., 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) null 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: (NSString) identificador del paquete
app_version: (NSString)
company: (NSString) subdomain
device_model: (NSString)
device_version: (NSString)
sdk_version: (NSString)
timestamp: (NSString) ISO 8601
Datos del menú de la fila
Metadatos
menu_id: NSString
menu_key: NSString, nullable
menu_name: NSString
menu_path : NSString
Datos de la sesión
Datos del menú de la fila
session_id: NSString
type: @"call", @"chat"
end_user_identifier: NSString
Cómo configurar Compartir pantalla
Si quieres usar la función de Compartir pantalla, integra UJETCobrowseKit.xcframework.
CocoaPods: Agrega el siguiente subspec al destino de tu app.
ruby
target 'MyApp' do
pod 'UJET'
pod 'UJET/Cobrowse'
end
Carthage: Agrega la siguiente línea en el archivo 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 configura la propiedad UJETGlobalOptions.cobrowseKey.
swift
let options = UJETGlobal
Options()options.cobrowseKey = cobrowseKey
UJET.setGlobalOptions(options)
Compartir pantalla completa del dispositivo (opcional)
Compartir la pantalla completa del dispositivo permite que los agentes de asistencia vean pantallas de aplicaciones externas. Esto suele ser útil cuando los agentes de asistencia al cliente necesitan verificar el estado de la configuración del sistema o ver cómo el usuario navega entre varias aplicaciones. Si no quieres usar esta función, puedes omitir esta sección.
Personaliza el cuadro de diálogo de consentimiento para compartir pantalla
Para personalizar el cuadro de diálogo de consentimiento de Screen Share, debes implementar el protocolo UJETCobrowseAlertProvider en tu clase de proveedor. En esta implementación, devuelve un UIViewController personalizado o cualquier otro objeto UIViewController heredado a través del método de protocolo respectivo. 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 ejemplo a continuación) y debe tener otro botón de rechazo. Invoca el cierre descartado cuando se hace 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 vistas 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 vistas personalizado para la alerta de solicitud de dispositivo completa debe tener RPSystemBroadcastPickerView y un cierre para pasar el estado 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.
Abre tu proyecto de Xcode.
Navega a File > Target.
Elige Broadcast Upload Extension.
Ingresa un Nombre para el objetivo.
Desmarca Include UI Extension.
Crea el destino y anota su ID de paquete.
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 el siguiente subspec 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 objetivo 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 llavero, de modo que están aisladas del resto del llavero de tus apps.
En ambos, tu destino de la app y tu destino de la extensión, agrega un derecho de uso compartido de Keychain para el grupo de Keychain de io.cobrowse.
Agrega el ID de paquete a tu plist
Toma el ID de paquete de la extensión que creaste anteriormente y agrega la siguiente entrada en el archivo Info.plist de tu app (nota: no en el archivo Info.plist de las extensiones), reemplazando el siguiente ID de paquete por el tuyo:
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 SampleHander.swift) como parte del destino que creaste anteriormente. Reemplaza el contenido de los archivos por lo siguiente:
Swift: Selecciona el producto UJETCobrowseExtension y agrégalo al destino de tu 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
Ahora puedes compilar y ejecutar tu app. La capacidad completa del dispositivo solo está disponible en dispositivos físicos y no funcionará en el simulador de iOS.
Cómo minimizar 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 deseas dirigir al usuario de vuelta a tu app después de que se recibió un evento del SDK, como un clic en una tarjeta de contenido. Para minimizar el SDK y dirigir a los usuarios 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
}
Soluciona problemas
Rechazo del envío de la app
Se rechazó el envío de la app porque se incluyó el framework de CallKit en el territorio de China.
Si Apple rechaza tu app por este motivo, solo 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 VoIP. Esta función está disponible 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 rastrear en GitHub
En este artículo, ofrecen dos opciones. Se recomienda usar Git LFS.
Si no usas bitcode, quitarlo del objeto binario puede ser otra opción. Ejecuta este comando en la carpeta UJETKit.xcframework.
xcrun bitcode_strip -r UJET -o UJET
dyld: Error de biblioteca no cargada
Agrega @executable_path/Frameworks en las rutas de acceso de búsqueda de Runpath 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 sobre IP habilitado:
¿Los usuarios pueden recibir llamadas VoIP en tu app?
Responde Sí 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 capacidad de voz por IP de Background Modes >.
Si eso no funciona, intenta compilar con un perfil de aprovisionamiento de distribución (ad hoc o Apple Store).
Envía una notificación push de prueba a tu app de prueba
Prepara tu certificado de VoIP y el token del dispositivo.
En el portal de la plataforma de CCAI, consulta la sección Push Notification Debug en el menú Configuración > Configuración del desarrollador.
Si ya configuraste el certificado para APNs, no tienes que volver a ingresarlo.
Ingresa tu certificado (opcional) y verifica si es de zona de pruebas o no (opcional), y, luego, ingresa el token del dispositivo de la notificación push de la app de prueba.
El inicio de un chat nuevo 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 ejemplo de configuración:
public func signPayload(_ payload: [AnyHashable: Any]?, payloadType: UjetPayloadType, success: (String?) -> Void, failure: (Error?) -> Void)
{
if payloadType == .customData {
success(nil)
}
}