O SDK para dispositivos móveis da plataforma Contact Center AI (plataforma CCAI) para o sistema operacional Apple iOS permite incorporar a experiência para dispositivos móveis da plataforma CCAI em aplicativos móveis iOS.
Requisitos
O SDK para dispositivos móveis iOS tem os seguintes requisitos:
- iOS 12.0 ou mais recente
Recuperar credenciais da empresa
Faça login no portal da Contact Center AI Platform (CCAI Platform) usando as credenciais de administrador.
Acesse Configurações > Configurações do desenvolvedor.
Em Chave da empresa e Senha secreta, anote a chave e a senha secreta da empresa.
Primeiros passos
A seguir, apresentamos um guia sobre como começar a usar o SDK para dispositivos móveis iOS da Plataforma CCAI.
Instalação
Para começar, instale o SDK do iOS.
Baixar o app de exemplo
Baixe o app de exemplo do iOS.
Acesse a pasta e instale as dependências usando o CocoaPods:
$ pod install --project-directory=ExampleAppPara configurar rapidamente as configurações do projeto, execute um script shell:
$ ./setup.shComo alternativa, é possível editar manualmente as configurações do projeto seguindo estas etapas:
Abra
ExampleApp.xcworkspace.Substitua os valores
UJETCompanyKeyeUJETCompanySecretemInfo.plistpela Chave da empresa e pelo Código secreto da empresa na página Configurações > Configurações do desenvolvedor do portal da plataforma CCAI.Substitua o valor
UJETSubdomainemInfo.plistpelo subdomínio no URL do portal da plataforma CCAI. O subdomínio precede diretamente.ujet.comno URL. Por exemplo,your-subdomainemhttps://your-subdomain.ujet.com/settings/developer-setting.
Integrar ao seu projeto
A integração do SDK do iOS com seu aplicativo depende do ambiente de desenvolvimento.
Gerenciador de pacotes do Swift
Adicione o pacote Swift para o SDK do iOS.
Nas configurações de build, coloque -ObjC em "Outras flags do vinculador".
Na versão mais recente do Xcode (atualmente 13.2), há um problema conhecido com o consumo de frameworks binários distribuídos usando o Gerenciador de pacotes do Swift. A solução alternativa atual para esse problema é adicionar uma fase de script de execução às fases de build do projeto Xcode. Essa fase de script de execução precisa vir depois da fase de criação de estruturas de incorporação. Essa nova fase de execução de script precisa conter o seguinte 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
Adicione a seguinte linha ao Podfile:
pod 'UJET', :podspec => 'https://sdk.ujet.co/ios/x.y.z/ujet.podspec' #specific version x.y.zExecute "pod install". Se o SDK do iOS já tiver sido integrado, execute pod update CCAI Platform.
Carthage
OGoogle Cloud recomenda usar um gerenciador de dependências ou integração manual porque uma dependência da plataforma CCAI não é compatível com o Carthage. Para fazer isso, adicione as seguintes linhas:
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
Integração manual
Isso não é compatível: https://github.com/twilio/conversations-ios/issues/12.
binary https://raw.githubusercontent.com/twilio/conversations-ios/master/twilio-convo-ios.json
Execute
carthage bootstrap --use-xcframeworksoucarthage update --use-xcframeworks(se você estiver atualizando dependências).Baixe
UJETKit.xcframework,UJETFoundationKit.xcframework,UJETChatRedKit.xcframework,UJETChatBlueKit.xcframework,UJETTwilioCallKit.xcframeworke todas as dependênciasTwilioVoice.xcframeworkeTwilioConversationsClient.xcframework.Arraste UJETKit.xcframework para a seção "Frameworks, Libraries, and Embedded Content" (Frameworks, bibliotecas e conteúdo incorporado) para adicionar ao destino.
Repita as etapas 2 e 3 em todas as dependências da etapa 1.
Em Configurações de build, coloque
-ObjCemOther Linker Flags.Adicione
libc++.tbdcomo uma dependência na seçãoLinked Frameworksdo destino.
Se você for criar o SDK manualmente com o projeto de exemplo, siga as etapas na seção a seguir.
Criar o SDK manualmente com o projeto de exemplo
Siga estas etapas em ordem:
Faça o download de todas as estruturas, incluindo
UJETKit.xcframeworke outras dependências.Crie a pasta CCAI Platform na raiz do projeto e extraia todos os frameworks.
Selecione o destino
Objc-ManualouSwift-Manuale crie.
Framework de importação
As seções a seguir fornecem instruções sobre como importar o framework.
Projeto Objective-C
@import UJETKit;
Projeto Swift
swiftimport
UJETimport UJETKit
Inicializar o SDK
Inicialize a plataforma CCAI com UJET_COMPANY_KEY e 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;
}
Você pode mudar o nível de registro de verbose para error. O nível de registro padrão é
UjetLogLevelInfo.
[UJET.setLogLevel:UjetLogLevelVerbose];
Autenticação de usuário final
Acesse o SDK do iOS pelo app iOS.
Para garantir que o usuário final esteja autenticado, estamos apresentando o mecanismo de assinatura JWT.
O SDK do iOS vai pedir para assinar o payload quando a autenticação for necessária. Se a assinatura for bem-sucedida, o aplicativo vai trocar o JWT assinado pelo token de autenticação do usuário final. O bloco de sucesso ou falha precisa ser chamado antes que o delegado retorne.
Para usuários anônimos (identifier = nil), o aplicativo vai criar um UUID para o usuário. Se o usuário for autenticado posteriormente com um identificador, o aplicativo tentará mesclar os dois usuários com base no UUID.
Em UJETObject.h do projeto de exemplo:
@import UJETKit;
@interface UJETObject : NSObject <UJETDelegate>
Implemente signPayload: payloadType: success: failure: delegate method.
- (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;
}
Recomendamos assinar o payload no servidor de aplicativos, não no cliente.
Este exemplo usa assinatura local para fins de teste. Consulte
signDataInRemote: success: failure: no arquivo UJETObject.m.
Para mais informações, consulte Autenticação de usuário final do SDK.
Configurar notificações push
O aplicativo envia notificações push para solicitar ações inteligentes, como verificação e foto, além de informar uma chamada recebida. O aplicativo exige que dois tipos diferentes de certificados (VoIP e APNs) sejam salvos no Portal do administrador.
Preparar o certificado de serviços de VoIP
A documentação de referência está disponível para a notificação push de VoIP da Apple.
Crie e faça o download do certificado VoIP no site para desenvolvedores da Apple.
Clique duas vezes no certificado para adicioná-lo ao Keychain.
Inicie o aplicativo Acesso às Chaves no Mac.
Escolha a categoria "Meus certificados" na barra lateral à esquerda.
Clique com o botão direito do mouse em "Serviços de VoIP: certificado your.app.id".
No menu pop-up, escolha Exportar.
Salve como cert.p12 sem proteger com uma senha. Para isso, deixe o campo em branco.
Execute o comando a seguir no terminal.
openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -debug -showcertA parte de cima de cert.pem é o certificado, e a parte de baixo é a chave privada.
Verifique se o certificado está funcionando com o servidor de notificações push da Apple.
openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -debug -showcertsSe for bem-sucedido, ele vai retornar:
--- 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) ---Faça login no portal da plataforma CCAI com credenciais de administrador e acesse Configurações > Configurações do desenvolvedor > App móvel.
Preencha o certificado na seção "Certificado de serviços VoIP" e salve. Não se esqueça de incluir limites (
-----BEGIN-----e-----END-----) para o certificado e a chave privada.Marque a caixa de seleção "Sandbox" se você estiver executando um app com um perfil de provisionamento de desenvolvimento, como a depuração no Xcode. Se o app for arquivado para Ad hoc ou App Store e estiver usando um perfil de provisionamento de distribuição, desmarque a caixa de seleção "Sandbox".
Preparar o SSL do serviço de notificação push da Apple
O processo é semelhante ao dos certificados de serviço VoIP. Nesse caso, o certificado SSL do serviço de notificação push da Apple (sandbox e Production) é usado. Consulte a documentação do servidor de notificações remotas da Apple para saber como criar o certificado.
Como integrar notificações push
Em 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];
Adicione os seguintes métodos delegados ao implementar o arquivo de protocolo UIApplicationDelegate:
Imprima o token do dispositivo para testar as notificações 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];
}
}
Ativar notificações push
Selecione o destino e abra a guia Recursos.
Ative a opção Notificações push.
Testar notificações push
As seções a seguir fornecem orientações sobre como testar as notificações push.
Seção de depuração de notificações push
No portal do administrador, acesse Configurações > Configurações do desenvolvedor. Nesta página, encontre a seção Depuração de notificações push:

Copie e cole o token do dispositivo na área de texto à direita e selecione o app para dispositivos móveis correto.
Receber o token do dispositivo
Um exemplo de string de token de dispositivo é assim:
7db0bc0044c8a203ed87cdab86a597a2c43bf16d82dae70e8d560e88253364b7
As notificações push geralmente são definidas na classe que está em conformidade com o protocolo UIApplicationDelegate ou PKPushRegistryDelegate. Em algum momento, o token do dispositivo vai estar disponível para você. Você pode imprimir antes de passar para o SDK do iOS. Para receber o token do dispositivo, use o snippet 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
Depois de inserir o arquivo PEM do certificado e o token do dispositivo, clique no botão.
O resultado vai mostrar a mensagem Notificação push configurada com sucesso se a notificação push de teste foi entregue.
A entrega das notificações push não é 100% garantida, dependendo da conexão de rede do dispositivo.
Configurações do projeto
As seções a seguir descrevem as mudanças necessárias para configurar o projeto.
Recursos
Nas configurações de destino, ative os seguintes recursos:
Notificações push
Modos em segundo plano (verifique estes itens)
Áudio e AirPlay
Voz sobre IP
Info.plist
Para proteger a privacidade do usuário, qualquer app iOS vinculado no iOS 10.0 ou em versões mais recentes que acesse qualquer um dos microfones, biblioteca de fotos e câmera do dispositivo precisa declarar a intenção de fazer isso. Inclua as seguintes chaves com um valor de string no arquivo Info.plist do app e forneça uma string de finalidade para essa chave. Se o app tentar acessar qualquer um dos microfones, a biblioteca de fotos e a câmera do dispositivo sem uma string de finalidade correspondente, ele será encerrado.
NSMicrophoneUsageDescription: permite o acesso ao microfone para fazer chamadas e conversar com equipes de suporte ou solução de problemas, além de enviar vídeos com som relacionado a consultas sobre produtos.
NSCameraUsageDescription: permite que o cliente acesse a câmera para tirar e enviar fotos relacionadas à consulta de suporte ao cliente.
NSPhotoLibraryUsageDescription: permite que o cliente envie fotos relacionadas à consulta de suporte ao cliente.
NSFaceIDUsageDescription: permite o acesso à verificação usando o Face ID.
Iniciar o SDK do iOS
Adicione a seguinte linha onde você quer iniciar o SDK do iOS:
[UJET startWithOptions:nil];
Também é possível iniciar o SDK do iOS em um ponto específico do menu com esta chave usando um ponto de acesso direto:
UJETStartOptions *option = [[UJETStartOptions alloc] initWithMenuKey:@"MENU_KEY"];
[UJET startWithOptions:option];
O menuKey pode ser criado com um ponto de acesso direto (DAP, na sigla em inglês). Confira a seguir as etapas para criar uma DAP:
Faça login no portal da plataforma CCAI com credenciais de administrador.
Acesse Configurações > Fila.
Selecione qualquer fila na estrutura de menu.
Selecione Criar ponto de acesso direto.
Insira a chave no formulário de texto.
Clique em Salvar.

Limpar o cache local se os dados do usuário tiverem sido atualizados
Estamos armazenando em cache o token de autenticação no Keychain para reutilizar e fazer solicitações menos frequentes para assinar o payload do app host. O SDK vai usá-lo até que ele expire ou seja revogado pela chamada clearUserData. O app host é responsável por revogar esse cache sempre que os dados relacionados ao usuário forem alterados ou atualizados, como um evento de saída.
[UJET clearUserData];
Verificar se há uma sessão antes de iniciar a Contact Center AI Platform
Antes de iniciar uma sessão, verifique se não há uma sessão em andamento. Isso é especialmente importante quando o userId muda.
[UJET getStatus];
Se houver uma sessão em andamento, vamos pedir que o usuário retome a sessão ou cancele a ação:
if ([UJET getStatus] != UjetStatusNone) {
// Display alert to cancel login or resume existing session
}
Personalizar
Há várias opções de tema do SDK listadas em UJETGlobalTheme.h.
Defina o tema após [UJET initialize]. Por exemplo:
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];

O nome da empresa é extraído de Portal do administrador > Configurações > Detalhes da Central de suporte > Nome de exibição.
Você pode definir a imagem do logotipo em vez do nome da empresa assim:
theme.companyImage = [UIImage imageNamed:@"logo"];

Se a imagem for muito grande, ela será redimensionada para caber na área.
Strings
Também é possível personalizar strings substituindo o valor. Por exemplo, coloque este par chave-valor em Localizable.strings:
"ujet_greeting_title" = "Title";
"ujet_greeting_description" = "Description";

As strings personalizáveis disponíveis estão listadas no arquivo ujet.strings.
Modo escuro
Você pode especificar uma tonalidade da cor desejada para o modo escuro e melhorar a legibilidade das fontes.
@property (nonatomic, strong) UIColor \*tintColorForDarkMode;
Se você não definir a propriedade, UJETGlobalTheme.tintColor será usado para o modo escuro. Recomendamos definir essa propriedade se o app for compatível com o modo escuro. Consulte os seguintes artigos da Apple sobre como escolher a cor de tonalidade certa para o modo escuro:
Tema do chat
Para personalizar a tela de chat, use uma string JSON ou cada classe de tema.
Para referência, consulte o app de exemplo e remova o comentário do 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 dos cards de conteúdo
Você pode adicionar personalização para cards de conteúdo junto com a personalização do chat.
Você pode fazer isso usando o arquivo JSON (consulte a propriedade content_card) ou a classe 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 do card de formulário
É possível adicionar personalização para cards de formulário e para o chat. Para isso, use o arquivo JSON (consulte form_card property) ou a classe 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)
}
Configuração do formulário da Web
Para configurar a capacidade de formulário da Web, implemente o método ujetWebFormDidReceive
do protocolo UJETDelegate. Esse método recebe um evento (um dicionário
FormMessageReceivedEvent) como parâmetro, contendo informações
relacionadas ao formulário. O dicionário de eventos (FormMessageReceivedEvent) inclui a seguinte estrutura JSON:
{
"type": "form_message_received",
"smart_action_id": 1,
"external_form_id": "external_foobar"
"signature": "4868a7e1dcb5..."
}
Para processar o evento, faça o seguinte:
Extraia as informações relevantes do dicionário de eventos (
smart_action_id,external_form_idesignature).Gere um URI e uma assinatura para os dados do formulário.
Transmita os dados do formulário para o SDK como um dicionário
FormDataEventusandocompletion closure.Se ocorrer algum erro durante a geração de URI/assinatura, invoque o callback usando
callback.onError()com oError.
O dicionário (FormDataEvent) transmitido ao SDK precisa ter a seguinte estrutura:
{
"type": "form_data",
"signature": "4868a7e1dcb5...",
"data": {
"smart_action_id":1,
"external_form_id": "form_id",
"uri":"foobar"
}
}
A assinatura (HMAC-SHA:256) precisa ser gerada usando o data e assinada com a
chave secreta compartilhada. As chaves de objeto dos dados precisam estar em ordem alfabética antes
de gerar assinaturas, e o mesmo data precisa ser enviado ao SDK.
Transferência pós-sessão
Você pode adicionar personalização para VA pós-sessão junto com a personalização do chat.
Isso pode ser feito usando o arquivo JSON (consulte a propriedade post_session) ou a classe UJETChatPostSessionVaTheme. A largura da borda só pode ser 0 ou 1. Se você não quiser diferenciar a experiência do VA pós-sessão, defina containerColor como branco e a borda como 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)
}
Menu de ações do chat
Você pode adicionar personalização para o menu de ações do chat junto com a personalização
do chat. Isso pode ser feito usando o arquivo JSON (consulte a propriedade form_card) ou a classe 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)
}
Outras aparições
Você pode personalizar outras aparências, como tamanho da fonte e cor do plano de fundo.
theme.supportTitleLabelFontSize = 30;
theme.supportDescriptionLabelFontSize = 20;
theme.supportPickerViewFontSize = 30;
theme.staticFontSizeInSupportPickerView = YES;
theme.backgroundColor = UIColor.darkGrayColor;
theme.backgroundColorForDarkMode = UIColor.lightGrayColor;

CallKit
No iOS 10.0 e versões mais recentes, o CallKit é ativado para todas as chamadas.
Com o CallKit, uma chamada no app aparece com a tela de chamada e é mostrada no histórico de ligações do smartphone.
Para iniciar uma nova sessão de suporte da plataforma CCAI no histórico de chamadas, adicione o seguinte bloco ao 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;
}
O CallKit permite que um ícone de 40x40 seja mostrado na tela de bloqueio ao receber uma chamada enquanto o dispositivo está bloqueado. Coloque uma imagem no Xcassets chamada 'icon-call-kit'.
Configurar o SDK
É possível definir várias opções antes de iniciar o SDK.
Confira a classe UJETGlobalOptions para mais detalhes.
UJETGlobalOptions *options = [UJETGlobalOptions new];
options.fallbackPhoneNumber = @"+18001112222";
options.preferredLanguage = @"en";
[UJET setGlobalOptions:options];
Mostrar ou ocultar o botão de download da transcrição
Você pode configurar o SDK para mostrar ou ocultar o botão de download da transcrição no menu de opções de chat e na tela pós-chat.
O código a seguir mostra como configurar o botão "Baixar transcrição":
typedef NS_OPTIONS(NSUInteger, UJETChatDownloadTranscriptVisibilityOptions) {
UJETChatDownloadTranscriptVisibilityOptionsShowAll = 0,
UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu = 1 << 0,
UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen = 1 << 1,
UJETChatDownloadTranscriptVisibilityOptionsHideAll = UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu | UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen
};
@property (nonatomic, assign) UJETChatDownloadTranscriptVisibilityOptions transcriptVisibilityOptions;
Fallback de RPTC
Oferecemos fallback de PSTN para várias situações:
A rede móvel está off-line.
Não é possível acessar o back-end do aplicativo.
O VoIP não está disponível
A condição da rede não é boa o suficiente para se conectar. Consulte a propriedade UJETGlobalOptions.pstnFallbackSensitivity para mais detalhes.
Ocorreu uma falha durante a conexão devido à configuração do firewall ou a um problema do provedor.
Recomendamos definir o número da URA da sua empresa em UJETGlobalOptions.fallbackPhoneNumber. O formato recomendado é + seguido pelo código do país e número de telefone. Por exemplo, +18001112222.
Sensibilidade de substituição da RPTC
É possível ajustar o nível de sensibilidade da verificação da condição da rede para o fallback da PSTN.
@property (nonatomic, assign) float pstnFallbackSensitivity;
O valor precisa estar no intervalo de 0,0 a 1,0. Se definido como 1, a chamada sempre será conectada pela PSTN em vez de VoIP. A latência máxima e o limite mínimo de largura de banda são 10.000 ms e 10 KB/s, respectivamente, para o valor 0. Por exemplo, um valor de 0,5 significa uma latência e uma largura de banda mínimas de 5.000 ms e 15 KB/s, respectivamente.
Para configurar esse valor, siga estas etapas:
Faça login no portal da Plataforma CCAI como administrador.
Acesse Configurações > Configurações do desenvolvedor > Apps para dispositivos móveis.
Encontre a seção Limite de número de telefone alternativo. O valor padrão é 0,85.
Especifique o novo valor de limite.
Clique em Salvar.
Desativar as notificações push no nível global
Você pode desativar as notificações push no nível global. Definir a propriedade a seguir como false ignora todas as dependências de notificações push e impede que elas cheguem aos usuários finais:
@property (nonatomic, assign) BOOL allowsPushNotifications;
Ignorar o modo escuro
É possível ignorar o modo escuro no SDK da plataforma CCAI especificamente com esta propriedade:
@property (nonatomic, assign) BOOL ignoreDarkMode;
Ocultar barra de status
É possível controlar a visibilidade da barra de status com esta propriedade:
@property (nonatomic, assign) BOOL hideStatusBar;
Por padrão, o hideStatusBar é definido como false e visible.
Pular a pesquisa de CSAT
Você pode adicionar um botão que permite ao usuário pular a pesquisa de CSAT. O exemplo de código a seguir mostra como adicionar o botão:
let options = UJETGlobalOptions()
options.skipCsat = true
Personalizar o indicador de atividade
Você pode adicionar sua própria animação de carregamento (dentro de um UIView) ao SDK e substituir o UIActivityIndicatorView padrão. Implemente o
método ujet_activityIndicator do UJETDelegate e retorne a
visualização 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
}
Se você já definiu UIUserInterfaceStyle como Light no Info.plist do app para desativar o modo escuro completamente, ignore essa propriedade.
Idioma preferido
O SDK da plataforma CCAI usa a seguinte ordem de prioridade para determinar o idioma preferido.
Idioma selecionado na tela de apresentação do app.
O idioma padrão foi selecionado em
UJETGlobalOptions. É possível definir o idioma padrão com a propriedadepreferredLanguage. Os códigos de idioma compatíveis podem ser encontrados no arquivoUJETGlobalOptions.h.O idioma selecionado no dispositivo (em Configurações > Geral > Idioma e região) será usado quando for compatível com o app.
O dialeto mais próximo do idioma do dispositivo será usado quando o aplicativo não for compatível com o idioma do dispositivo, mas for compatível com o dialeto principal mais próximo. Por exemplo, se o usuário selecionar o espanhol de Cuba como idioma no dispositivo e o app não oferecer suporte a esse idioma, mas sim ao dialeto principal espanhol, o idioma espanhol será usado.
O inglês será usado se o idioma do dispositivo não for compatível com o app.
Configurar ícones de links de evasão externos
Para personalizar o ícone no canal de link de redirecionamento externo, faça upload dele no catálogo de recursos do app e use o mesmo nome ao criar o link em Configurações > Chat > Links de redirecionamento externo > Ver links > Adicionar link de redirecionamento no Portal do administrador. Se o nome do ícone no Portal do administrador não corresponder ao ícone enviado por upload para o app, o SDK vai usar o ícone padrão. Consulte este link sobre como adicionar imagens ao catálogo de recursos.



Fallback
Você pode usar a função didHandleUjetError para o fallback de erros
inesperados. Se você não usar essa função ou ela retornar false, o SDK do iOS
vai processar o erro.
A tabela a seguir mostra os erros que a função didHandleUjetError
detecta:
| Tipo de erro | Código do erro | Descrição |
|---|---|---|
networkError |
1 | A rede não está disponível. Observação:esse erro não é acionado quando a rede não está disponível durante uma sessão de chat ou chamada ou uma tela de classificação. |
authenticationError |
100 | Ocorreu um erro inesperado durante a autenticação. |
authenticationJwtError |
101 | Ocorreu um erro inesperado durante a validação do JWT, por exemplo, um erro de análise. |
voipConnectionError |
1000 | Não foi possível estabelecer uma conexão com o provedor de VoIP. Um callback do SDK VoIP processa isso. |
voipLibraryNotFound |
1001 | O sistema espera uma chamada para se conectar por um provedor de VoIP, mas não consegue encontrar um. Isso pode acontecer se você integrar o SDK errado ou não adicionar uma biblioteca de provedor de VoIP às suas dependências. |
chatLibraryNotFound |
1100 | Ocorre quando o sistema não consegue encontrar a biblioteca de chat. Isso pode acontecer quando você integra o SDK errado ou não adiciona uma biblioteca de chat do Twilio às dependências. |
O exemplo de código a seguir mostra como usar a função 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
}
}
Enviar dados personalizados para seu CRM
É possível enviar dados personalizados para o tíquete do CRM.
Há dois métodos para enviar dados personalizados:
Método seguro: assinatura de dados predefinida com JWT.
Método não seguro: dados predefinidos com JSON simples (não recomendado).
Usar o método seguro para enviar dados personalizados
Você precisa implementar o método de assinatura. Primeiro, você pode colocar seus dados personalizados no lado do cliente e enviá-los ao servidor para assinatura. No seu servidor, é possível adicionar mais dados definindo o formulário, assinando com company.secret e retornando-o 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];
}
}
Usar um método sem segurança para enviar dados personalizados
Esse método não é recomendado porque cria uma possível vulnerabilidade que
pode abrir seu aplicativo para um ataque "man-in-the-middle". Se você optar por usar esse método, não seremos responsáveis pela exposição à segurança e pelos possíveis danos que possam ocorrer. Recomendamos que você use o método seguro descrito anteriormente para enviar dados personalizados no seu aplicativo. Ou você pode iniciar o SDK do iOS com a instância UJETCustomData. Nesse caso, o delegado signPayload para
UJETPayloadCustomData só precisa chamar 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];
Usar dados personalizados não assinados para enviar uma transcrição de chat externa
Você pode enviar a transcrição da conversa para o SDK quando ela for iniciada com dados personalizados não assinados chamando o método setExternalChatTransfer: ou setExternalChatTransferWithDictionary: para definir dados JSON com NSString ou NSDictionary, respectivamente.
UJETCustomData *customData = [UJETCustomData new];
[customData setExternalChatTransfer:jsonString];
UJETStartOptions *options = [UJETStartOptions new];
options.unsignedCustomData = customData;
[UJET startWithOptions:options];
Formato JSON:
greeting_override: string
agente: dicionário
name: string
avatar: string [url of agent avatar, optional]
transcrição: matriz
sender: string ["end_user" or "agent"]
timestamp: string [por exemplo, "2021-03-15 12:00:00Z"]
content: array
type: string [one of text, media]
text: string [obrigatório para o tipo de texto]
media: dicionário [obrigatório para tipo de mídia]
type: string [uma de image, video]
url: string [URL público que aponta para o arquivo de mídia]
Exemplo 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"
}
]
}
]
}
Você pode usar o Markdown no tipo de texto. A sintaxe aceita inclui itálico, negrito, lista com marcadores, hiperlink e sublinhado (--text--).
Exemplo de dados personalizados { :#example-of-custom-data }
JSON codificado para JWT
O arquivo JSON precisa validar o JWT. E o objeto de dados personalizados é o valor da chave "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 dado é semelhante ao formato de objeto JSON e precisa conter a chave, o valor, o tipo e o rótulo.
A chave é o identificador exclusivo dos dados. O rótulo é o nome de exibição na página do CRM. O tipo é o tipo do valor.
string
- String JSON
número
- número inteiro, ponto flutuante
data
- Formato de carimbo de data/hora Unix UTC com 13 dígitos. (contém milissegundos)
URL
- Formato do URL HTTP
Exemplo de CRM

Local
Use o framework CoreLocation. Para mais detalhes, consulte AppDelegate.m.
Versão do SO do dispositivo
[customData set:@"os_version" label:@"OS Version" stringValue:[[UIDevice currentDevice] systemVersion]];
Impedir a exibição de dados personalizados
É possível usar a propriedade invisible_to_agent com um objeto de dados personalizado para impedir que dados personalizados assinados ou não assinados sejam mostrados no adaptador do agente. No exemplo anterior, o CPF do usuário final não é
mostrado no adaptador do agente porque "invisible_to_agent" : true está incluído no
objeto ssn.
Ao incluir a propriedade "invisible_to_agent" : true com um objeto de dados personalizado, você pode esperar o seguinte comportamento:
- Os dados personalizados são incluídos no arquivo de metadados da sessão.
- Os dados personalizados não são incluídos nos registros do CRM.
Para mais informações, consulte Ver dados da sessão no adaptador do agente.
Propriedades de dados reservadas
É possível enviar propriedades de dados reservados para a Contact Center AI Platform (plataforma CCAI) como dados personalizados assinados quando uma sessão começa. Para mais informações, consulte Enviar propriedades de dados reservadas.
Confira a seguir um exemplo de propriedades de dados reservadas em dados 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"
}
}
}
Substitua:
VERIFIED_CUSTOMER_BOOLEAN: "True" se você considerar que esse usuário final é um cliente legítimo.VERIFIED_BAD_ACTOR_BOOLEAN: verdadeiro se você considerar que esse usuário final pode ser um usuário de má-fé.REPEAT_CUSTOMER_BOOLEAN: verdadeiro se você determinou que esse usuário final já entrou em contato com sua central de atendimento.
Personalizar fluxo
Desconectar a plataforma CCAI para processar eventos do app host
// CCAI Platform is connected
...
// An event has come
[UJET disconnect:^{
// Handle an event
}];
Adiar uma chamada ou um chat recebido da plataforma CCAI
Implementar um método delegado para processar eventos recebidos
- (BOOL)shouldConnectUjetIncoming:(NSString *)identifier forType:(UjetIncomingType)type {
if (weDoingSomething) {
// save identifier and type
return NO; // postpone
} else {
return YES;
}
}
Conectar evento adiado
[UJET connect:identifier forType:UjetIncomingTypeCall];
Configurar link direto
Isso permite que os agentes em chamadas da PSTN usem ações inteligentes por SMS, tanto quando um usuário final tem ou não o app.
Acesse Configurações > Gerenciamento de operações > Ativar o envio de SMS para baixar o app no portal da plataforma CCAI.
Você pode definir o URL do app com sua página da Web (por exemplo, https://your-company.com/support) depois de configurar o Universal Link ou o esquema de URL personalizado. Você pode selecionar qualquer uma das opções.
Implementar o método delegado para processar links diretos
O link universal e o URL personalizado são semelhantes a
https://your-company.com/support?call_id=x&nonce=y e
your-company://support?call_id=x&nonce=y, respectivamente. Coloque um dos seus links sem parâmetros de consulta em "URL do app" no Portal do administrador. Por exemplo, coloque
your-company://support se estiver usando um esquema de URL personalizado.
No método delegado, chame [UJET start] somente quando os caminhos e parâmetros de URL no link universal ou no URL personalizado forem específicos para a plataforma da 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];
}
...
}
Se o app adotar UIWindowSceneDelegate, adicione este snippet 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 mais informações, consulte os exemplos de código do arquivo UJETObject+DeepLink.
Observar o evento da plataforma CCAI
Postamos os seguintes eventos usando NSNotificationCenter.defaultCenter. Você pode ouvir e personalizar o fluxo de acordo com seu caso de uso, por exemplo, um layout de teclado personalizado.
UJETEventEmailDidClick
- Dados do menu da fila
UJETEventEmailDidSubmit
Dados do menu da fila
has_attachment: (NSNumber) @YES, @NO
UJETEventSessionViewDidAppear
type: @"call", @"chat"
timestamp: (NSString) ISO 8601
UJETEventSessionViewDidDisappear
type: @"call", @"chat"
timestamp: (NSString) ISO 8601
UJETEventSessionDidCreate
- Dados da sessão
UJETEventSessionDidEnd
Dados da sessão
agent_name: (NSString) null se o agente não entrou
duration: (NSNumber) somente para chamadas
ended_by: (NSString)
type=call: @"agent", @"end_user"
type=chat: @"agent", @"end_user", @"timeout", @"dismissed"
UJETEventSdkDidTerminate
UJETEventPostSessionOptInDidSelected
- opt_in_selected: (NSString) @"Yes", @"No"
Dados do evento
Metadados
application: @"iOS"
app_id: (NSString) identificador do pacote
app_version: (NSString)
company: (NSString) subdomain
device_model: (NSString)
device_version: (NSString)
sdk_version: (NSString)
timestamp: (NSString) ISO 8601
Dados do menu da fila
Metadados
menu_id: NSString
menu_key: NSString, nullable
menu_name: NSString
menu_path : NSString
Dados da sessão
Dados do menu da fila
session_id: NSString
type: @"call", @"chat"
end_user_identifier: NSString
Configurar o Compartilhamento de tela
Se quiser usar o recurso de compartilhamento de tela, integre
UJETCobrowseKit.xcframework.
CocoaPods:adicione a seguinte subespecificação à meta do app.
ruby
target 'MyApp' do
pod 'UJET'
pod 'UJET/Cobrowse'
end
Carthage:adicione a seguinte linha ao Cartfile:
binary "https://sdk.ujet.co/ios/UJETKit.json"
SwiftPM:selecione os produtos UJET e UJETCobrowse e adicione ao destino do app.
e defina a propriedade UJETGlobalOptions.cobrowseKey.
swift
let options = UJETGlobal
Options()options.cobrowseKey = cobrowseKey
UJET.setGlobalOptions(options)
Compartilhamento de tela do dispositivo completo (opcional)
Com o compartilhamento de tela do dispositivo completo, seus agentes de suporte podem ver telas de aplicativos externos. Isso é útil quando os representantes de suporte precisam verificar o estado das configurações do sistema ou ver o usuário navegar entre vários aplicativos. Se não quiser esse recurso, pule esta seção.
Personalizar a caixa de diálogo de consentimento para o compartilhamento de tela
Para personalizar a caixa de diálogo de consentimento do compartilhamento de tela, implemente o protocolo
UJETCobrowseAlertProvider na classe do provedor. Nessa
implementação, retorne um UIViewController personalizado ou qualquer outro objeto herdado
UIViewController pelo respectivo método de protocolo. UIViewController
precisa ter dois botões, um para aceitar e outro para negar.
Depois de receber o consentimento, transmita para nosso SDK chamando o encerramento
consentStatus. UIViewController de cobrowseFullDeviceRequestAlert. O
delegado precisa conter o RPSystemBroadcastPickerView com título (veja no
código de exemplo abaixo) e ter outro botão de negar. Invoque o fechamento
descartado ao clicar no botão "Negar".
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
}
}
O controlador de visualização personalizado precisa ter um encerramento para transmitir o status de consentimento ao 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)
}
}
}
O controlador de visualização personalizado para o alerta de solicitação de dispositivo completo precisa ter
RPSystemBroadcastPickerView e um fechamento para transmitir o status dismiss ao
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?()
}
}
}
Não se esqueça de transmitir esse provedor ao nosso SDK usando a seguinte API:
let provider = CobrowseAlertProvider()
UJET.setCobrowseAlertProvider(provider)
Extensão de transmissão
O recurso exige a adição de uma extensão de transmissão.
Abra seu projeto do Xcode.
Navegue até Arquivo > Destino.
Escolha Extensão de envio de transmissão.
Digite um Nome para o destino.
Desmarque Incluir extensão de interface.
Crie o destino e anote o ID do pacote.
Mude o SDK de destino da sua extensão de transmissão para iOS 12.0 ou mais recente.
Integre o SDK
CocoaPods:adicione a seguinte subespecificação ao destino da extensão:
target 'MyApp' do
pod 'UJET'
pod 'UJET/Cobrowse'
end
target 'MyAppExtension' do
pod 'UJET/CobrowseExtension'
end
Se você estiver usando o SwiftPM, selecione o produto UJETCobrowseExtension e adicione à sua
extensão de destino.
Configurar o compartilhamento de chaves
Seu app e a extensão de app criada anteriormente precisam compartilhar alguns segredos pelo conjunto de chaves do iOS. Eles fazem isso usando o próprio grupo de chaves para ficar isolados do restante do conjunto de chaves dos apps.
No destino do app e no destino da extensão, adicione um direito de
compartilhamento do Keychain para o grupo de keychain io.cobrowse.
Adicionar o ID do pacote ao plist
Pegue o ID do pacote da extensão que você criou anteriormente e adicione a seguinte entrada no Info.plist dos seus apps (observação: não no Info.plist das extensões), substituindo o ID do pacote a seguir pelo seu:
xml
<key>CBIOBroadcastExtension</key>
<string>your.app.extension.bundle.ID.here</string>
Implementar a extensão
O Xcode adicionou os arquivos SampleHandler.m e SampleHandler.h (ou
SampleHander.swift) como parte do destino criado anteriormente. Substitua o conteúdo dos arquivos pelo seguinte:
Swift:selecione o produto UJETCobrowseExtension e adicione ao destino
da extensão:
import CobrowseIOAppExtension
class SampleHandler: CobrowseIOReplayKitExtension {
}
ObjC
objc// SampleHandler.h
@import CobrowseIOAppExtension;
@interface SampleHandler : CobrowseIOReplayKitExtension
@end// SampleHandler.m
#import "SampleHandler.h"
@implementation SampleHandler
@end
Criar e executar seu app
Agora você pode criar e executar seu app. A capacidade total do dispositivo só está disponível em dispositivos físicos e não funciona no simulador do iOS.
Minimizar o SDK
O SDK da Contact Center AI Platform pode ser minimizado quando uma sessão de chat ou uma chamada está em andamento. Isso pode ser útil quando você quer direcionar o usuário de volta ao seu app depois que um evento do SDK é recebido, como um clique em um card de conteúdo. Para minimizar o SDK e direcionar o usuário de volta ao app, use:
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
}
Solução de problemas
Rejeição do envio de app
O envio do app foi rejeitado devido à inclusão da estrutura CallKit no território da China.
Se o app for rejeitado pela Apple por esse motivo, deixe um comentário, já que o sistema foi projetado para desativar o framework CallKit na região da China em uma chamada VoIP. Isso é válido a partir da versão 0.31.1 do SDK.
O tamanho do SDK é muito grande
Quando o tamanho do SDK é muito grande e difícil de rastrear no GitHub
Neste artigo, eles oferecem duas opções. Recomendamos usar o Git lfs.
Se você não estiver usando o Bitcode, remover o Bitcode do binário pode ser outra opção. Execute este comando na pasta UJETKit.xcframework.
xcrun bitcode_strip -r UJET -o UJET
dyld: erro de biblioteca não carregada
Adicione @executable_path/Frameworks em "Runpath Search Paths" em Target > Build Settings > Linking.
Envio de apps no iTunes Connect
A Apple pode fazer a seguinte pergunta durante o processo de revisão devido ao modo em segundo plano de voz sobre IP ativado:
Os usuários podem receber chamadas VoIP no seu app?
Responda Sim à pergunta.
A notificação de alerta fica indisponível ao iniciar o SDK
Verifique se:
Use um dispositivo real, não um simulador.
Ative as notificações push e a Capacidade de modos em segundo plano > Voz sobre IP.
Se isso não ajudar, tente criar com o perfil de provisionamento de distribuição (Ad-hoc ou Apple Store).
Testar notificações push no app de teste
Prepare o certificado de VoIP e o token do dispositivo.
No portal da plataforma CCAI, consulte a seção Depuração de notificações push no menu Configurações > Configurações do desenvolvedor.
Se você já tiver definido o certificado para APNS, não será necessário inserir o certificado novamente.
Insira seu certificado (opcional) e verifique se é sandbox ou não (opcional), além de inserir o token do dispositivo de notificação push do app de teste.
Iniciar um novo chat levou mais de 30 segundos
Verifique se você está respondendo a um método delegado de dados personalizados. Retorne dados personalizados válidos mediante solicitação ou apenas retorne nil no bloco de sucesso.
Use este snippet de código como um exemplo de configuração:
public func signPayload(_ payload: [AnyHashable: Any]?, payloadType: UjetPayloadType, success: (String?) -> Void, failure: (Error?) -> Void)
{
if payloadType == .customData {
success(nil)
}
}