The CCAI Platform mobile SDK for the Apple iOS operating system provides the ability to embed the CCAI Platform mobile experience within iOS mobile applications.
Requirements
The iOS mobile SDK has the following requirements:
- iOS 12.0+
Retrieve company credentials
Sign into the Contact Center AI Platform (CCAI Platform) portal using Admin credentials.
Go to Settings > Developer Settings.
Under Company Key and Secret Code, note Company Key and Company Secret Code.
Getting started
The following provides a guide on how to get started with the CCAI Platform iOS mobile SDK.
Installation
To start get started you need to install the iOS SDK. Guidance on this are in the following sections.
Download the Example App
Download the iOS Example App.
Navigate to the folder and install dependencies using CocoaPods:
$ pod install --project-directory=ExampleApp
To quickly configure project settings, run a shell script:
$ ./setup.sh
Alternatively, you can manually edit project settings by following these steps:
Open
ExampleApp.xcworkspace
.Replace the
UJETCompanyKey
andUJETCompanySecret
values inInfo.plist
with the Company Key and Company Secret Code values from the Settings > Developer Settings page in the CCAI Platform portal.Replace the
UJETSubdomain
value inInfo.plist
with the subdomain in the URL for your CCAI Platform portal. The subdomain directly precedes.ccaiplatform.com
in the URL— for example,your-subdomain
inhttps://your-subdomain.ccaiplatform.com/settings/developer-setting
.
Integrate to your project
Integration of the iOS SDK with your application will depend on your development environment. Outlined below instructions for:
Swift package manager
CocoaPods
Carthage
Swift package manager
Add the https://customer:glpat-TwLqiBEKX54hzDyCgK_s@gitlab.com/ujet/ujet-ios-sdk-sp.git as a Swift Package.
In your Build Settings, put -ObjC on Other Linker Flags.
As of the latest release of Xcode (currently 13.2), there is a known issue with consuming binary frameworks distributed via Swift Package Manager. The current workaround to this issue is to add a Run Script Phase to the Build Phases of your Xcode project. This Run Script Phase should come after the Embed Frameworks build phase. This new Run Script Phase should contain the following code:
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
Add the following line to the Podfile:
pod 'UJET', :podspec => 'https://sdk.ujet.co/ios/x.y.z/CCAI Platform.podspec' #specific version x.y.z
Run pod install. If the iOS SDK has been integrated previously, run pod update CCAI Platform instead.
Carthage
Google Cloud recommends using a dependency manager or manual integration because a CCAI Platform dependency does not currently support Carthage. To do this, add the following lines:
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
Manual Integration
This is not supported: https://github.com/twilio/conversations-ios/issues/12.
binary https://raw.githubusercontent.com/twilio/conversations-ios/master/twilio-convo-ios.json
Run
carthage bootstrap --use-xcframeworks
(orcarthage update --use-xcframeworks
(if you're updating dependencies).Download
UJETKit.xcframework
,UJETFoundationKit.xcframework
,UJETChatRedKit.xcframework
,UJETChatBlueKit.xcframework
,UJETTwilioCallKit.xcframework
, and all dependenciesTwilioVoice.xcframework
andTwilioConversationsClient.xcframework
.Add the UJETKit.xcframework to your target by dragging it into the Frameworks, Libraries, and Embedded Content section.
Repeat Steps 2 and 3 on all dependencies from Step 1.
In your Build Settings, put
-ObjC
onOther Linker Flags
.Add
libc++.tbd
as a dependency inLinked Frameworks
section of target.
If you are going to build the SDK manually with the example project use the steps in the section below.
Build the SDK manually with the example project
Follow these steps in order:
Download all frameworks including
UJETKit.xcframework
and other dependencies.Create folder CCAI Platform on the project root and unzip all frameworks.
Select
Objc-Manual
orSwift-Manual
target and build.
Import framework
The following sections provide instructions on how to import the framework.
Objective-C Project
@import UJETKit;
Swift Project
swiftimport
UJETimport UJETKit
Initialize SDK
Initialize CCAI Platform with UJET_COMPANY_KEY
and UJET_SUBDOMAIN
.
In application:didFinishLaunchingWithOptions: method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize CCAI Platform
[CCAI Platform initialize:UJET_COMPANY_KEY subdomain:UJET_SUBDOMAIN delegate:self];
// YOUR CODE
return YES;
}
You can change log level from verbose
to error
. The default log level is
UjetLogLevelInfo
.
[CCAI Platform setLogLevel:UjetLogLevelVerbose];
End-user authentication
Access the iOS SDK through the iOS app.
In order to make sure that the end user is authenticated, we are introducing the JWT signing mechanism.
The iOS SDK will ask to sign the payload when authentication is needed. If the signing is successful, the application exchanges the signed JWT to the end user auth token. The success or failure block must be called before the delegate returns.
For anonymous user (identifier = nil), the application will create a UUID for the user. If at a later time the user is authenticated with an identifier, the application will attempt to merge the two users based on the UUID.
In UJETObject.h
from example project:
@import UJETKit;
@interface UJETObject : NSObject <UJETDelegate>
Implement 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;
}
We strongly recommend signing the payload from your application server, not in the client.
This example uses local signing for testing purposes. See
signDataInRemote: success: failure:
in the UJETObject.m
file.
For more information, see SDK End user authentication.
Set up push notifications
The application sends push notifications to request Smart Actions like verification and photo, as well as reporting an incoming call. The application requires two different types of certificates (VoIP and APNs) to be saved in the Admin Portal.
Prepare VoIP services certificate
Reference documentation is available for Apple's VoIP push notification.
Create and download the VoIP certificate from the Apple developer site.
Double-click the certificate to add it to Keychain.
Start the Keychain Access application on your Mac.
Pick the My Certificates category in the left hand sidebar.
Right-click VoIP Services: your.app.id certificate.
In the popup menu choose Export.
Save it as cert.p12 without protecting it with a password by leaving the password blank.
Run the following command in terminal.
openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -debug -showcert
The upper part of cert.pem is the certificate and the lower part is the private key.
Check that your certificate is working with Apple's push notification server.
openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -debug -showcerts
When successful, it should return:
--- 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) ---
Sign in to the CCAI Platform portal with admin credentials, and go to Settings > Developer Settings > Mobile App.
Fill the certificate in the 'VoIP Services Certificate' section, and save. Be sure to contain boundaries (
-----BEGIN-----
and-----END-----
) for both certificate and private key.Check the Sandbox checkbox if you are running an app with a development provisioning profile such as debugging in Xcode. If your app is archived for Ad hoc or App store and is using a distribution provisioning profile, then uncheck the Sandbox checkbox.
Prepare Apple push notification service SSL
The process for this is similar to that for VOIP service certificates. In this case Apple push notification service SSL (Sandbox & Production) certificate is used. You can refer to the Apple remote notification server documentation for guidance on how to create the certificate.
Integrating push notification
In AppDelegate.m
:
@import PushKit;
@interface AppDelegate() <PKPushRegistryDelegate>
In application:didFinishLaunchingWithOptions: method:
// Initialize CCAI Platform
[CCAI Platform 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];
Add the following delegate methods in implementing UIApplicationDelegate
protocol file:
Please print your device token to test push notifications.
// PKPushRegistryDelegate
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type {
[CCAI Platform updatePushToken:credentials.token type:UjetPushTypeVoIP];
}
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
if (payload.dictionaryPayload[@"ujet"]) {
[CCAI Platform receivedNotification:payload.dictionaryPayload completion:completion];
} else {
completion();
}
}
// UIApplicationDelegate
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[CCAI Platform updatePushToken:deviceToken type:UjetPushTypeAPN];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
if (userInfo[@"ujet"]) {
[CCAI Platform 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) {
[CCAI Platform 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) {
[CCAI Platform receivedNotification:userInfo completion:nil];
}
}
Enable push notifications capability
Select your target and open Capabilities tab.
Turn on the switch of Push Notifications.
Test push notifications
The following sections provide guidance on how to test the push notifications.
Push notification debug section
In the admin portal, navigate to Settings > Developer Settings. On this page, find the section titled Push Notification Debug:
Copy and paste the device token in the right text area and select the right Mobile App.
Get the device token
An example device token string looks like this:
7db0bc0044c8a203ed87cdab86a597a2c43bf16d82dae70e8d560e88253364b7
Push notifications are usually set in the class which conforms to UIApplicationDelegate or PKPushRegistryDelegate protocol. At some point, the device token is available to you. You can print it out before passing it to the iOS SDK. To get your device token, use the code snippet below.
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]);
}
Result
Once you have entered the certificate PEM file and the device token, click the button.
The result will show a message Push notification successfully configured if the test push notification was successfully delivered.
Push notification is not 100% guaranteed to be delivered, depending on the device's network connection.
Project configurations
The following sections outline the changes needed to configure the project.
Project Configuration
Capabilities
In target settings, turn on below capabilities:
Push Notifications
Background Modes (check below items)
Audio and AirPlay
Voice over IP
Info.plist
To protect user privacy, any iOS app linked on or after iOS 10.0 which accesses any of the device's microphones, photo library, and camera, must declare the intent to do so. Include the keys below with a string value in your app's Info.plist file and provide a purpose string for this key. If your app attempts to access any of the device's microphones, photo library and camera without a corresponding purpose string, the app exits.
NSMicrophoneUsageDescription: Allows access to the microphone for calling and talking to support/troubleshooting teams, and for sending videos with sound related to product inquiries.
NSCameraUsageDescription: Allows access to the camera for customer to take and send photos related to their customer support inquiry.
NSPhotoLibraryUsageDescription: Allows access for customer to send photos related to their customer support inquiry.
NSFaceIDUsageDescription: Allows access to verification using Face ID.
Start the iOS SDK
Add the following line where you want to start the iOS SDK:
[CCAI Platform startWithOptions:nil];
You can also start the iOS SDK from a specific point in the menu with this key using a Direct Access Point:
UJETStartOptions *option = [[UJETStartOptions alloc] initWithMenuKey:@"MENU_KEY"];
[CCAI Platform startWithOptions:option];
The menuKey
can be created by creating a Direct Access Point (DAP). The
Following steps provide direction on how to create a DAP:
Sign in to the CCAI Platform portal with in with administrator credentials.
Go to Settings > Queue.
Select any queue from the menu structure.
Select Create direct access point
Enter key in the text form.
Click Save.
Clear cache from local if user data has been updated
We're caching auth token in the Keychain to re-use and make less frequent requests to sign payload from the host app. The SDK will use it until expired or revoked through clearUserData call. The host app is in charge of revoking this cache whenever user related data has changed or updated such as a sign out event.
[CCAI Platform clearUserData];
Check for existing session before starting UJET
Before starting a session, check to see if there isn't a current session. This is especially important when the userId has changed.
[CCAI Platform getStatus];
If there is an existing session, we should prompt the user to resume the session or cancel the action:
if ([CCAI Platform getStatus] != UjetStatusNone) {
// Display alert to cancel login or resume existing session
}
Customization
There are several options for the SDK theme listed in UJETGlobalTheme.h.
Set your theme after [CCAI Platform initialize] for example,
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];
[CCAI Platform setGlobalTheme:theme];
The company name is retrieved from Admin Portal > Settings > Support Center Details > Display Name.
You can set the logo image instead of the company name like this:
theme.companyImage = [UIImage imageNamed:@"logo"];
The image will be resized to fit on the area if it is too large.
Strings
You can also customize strings by overriding the value. For example, put this key/value on your Localizable.strings:
"ujet_greeting_title" = "Title";
"ujet_greeting_description" = "Description";
Available customizable strings are listed in ujet.strings file.
Specify dark mode for better legibility
You can specify a tint of the color you want for dark mode to better legibility of fonts.
@property (nonatomic, strong) UIColor \*tintColorForDarkMode;
If you don't set the property then UJETGlobalTheme.tintColor will be used for dark mode. We recommend setting this property if your app supports dark mode. Refer to the following Apple articles on how to pick the right tint color for dark mode:
Other appearances
You can customize other appearances such as font size and background color.
theme.supportTitleLabelFontSize = 30;
theme.supportDescriptionLabelFontSize = 20;
theme.supportPickerViewFontSize = 30;
theme.staticFontSizeInSupportPickerView = YES;
theme.backgroundColor = UIColor.darkGrayColor;
theme.backgroundColorForDarkMode = UIColor.lightGrayColor;
CallKit
On iOS 10.0 and greater, CallKit is enabled for all calls.
With CallKit, it shows an in-app call coming in with the call screen and shows the call in the phone's call history.
To start a new CCAI Platform support session from call history, add the following block to your 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
[CCAI Platform startWithOptions:nil];
}
return YES;
}
CallKit allows a 40x40 icon to be displayed on the lock screen when receiving a call while the device is locked. Place an image in your Xcassets named 'icon-call-kit'.
Configure SDK
You can set several options before starting the SDK.
Please take a look at the UJETGlobalOptions class for details.
UJETGlobalOptions *options = [UJETGlobalOptions new];
options.fallbackPhoneNumber = @"+18001112222";
options.preferredLanguage = @"en";
[CCAI Platform setGlobalOptions:options];
PSTN Fallback
We provide PSTN fallback for several situations:
Mobile network is offline.
The application backend is not reachable.
VoIP is not available
The network condition is not good enough to connect. See UJETGlobalOptions.pstnFallbackSensitivity property for details.
A failure has occurred during connection due to firewall configuration or provider issue.
We recommend setting your company IVR number in UJETGlobalOptions.fallbackPhoneNumber. The recommended format is + followed by country code and phone number. eg. +18001112222.
PSTN Fallback Sensitivity
You can adjust the sensitivity level of checking network condition to PSTN fallback.
@property (nonatomic, assign) float pstnFallbackSensitivity;
The value must be in the range of 0.0 to 1.0. If set to 1, the call will always connect through the PSTN rather than VoIP. The maximum latency and the minimum bandwidth threshold are 10000ms and 10KB/s respectively for the value of 0. For example, a value of 0.5 means a minimum latency and bandwidth is 5000 ms and 15KB/s, respectively.
This value can be configured by following these steps:
Sign in to the CCAI Platform portal as an administrator.
Go to Settings > Developer Settings > Mobile Apps.
Find the section Fallback phone number threshold. The default value is 0.85.
Specify the new threshold value.
Click save.
Ignore dark mode
You can ignore the dark mode in CCAI Platform SDK specifically with this property:
@property (nonatomic, assign) BOOL ignoreDarkMode;
If you've already set UIUserInterfaceStyle as Light on your app's Info.plist to opt out of dark mode entirely, then you can ignore this property.
Preferred language
The CCAI Platform SDK will use the following priority order to determine the preferred language.
Language selected from the splash screen within the app.
Default language selected from
UJETGlobalOptions
. You can set the default language withpreferredLanguage
property. The supported language codes can be found in theUJETGlobalOptions.h
file.Device language selected in the device (from Settings > General > Language & Region) will be used, when it is supported by the app.
Closest dialect of device language will be used when the application does not support the device language but supports its closest parent dialect. For example, if the user selected Spanish Cuba as the language in the device and the app does not support Spanish Cuba but supports parent dialect Spanish, then Spanish language will be used.
English will be used if the device language is not supported by the app.
Configure external deflection link icons
You can customize the icon in the external deflection link channel by uploading icon into asset catalog of your app and ensure to use the same icon name while creating external deflection link in Settings > Chat > External Deflection Links > View links > Add Deflection Link in the Admin Portal. If the icon name in the Admin Portal does not match with the icon uploaded into the app then the SDK will use the default icon. You can refer to this link on how to add images on asset catalog.
Send custom data to your CRM
You can send custom data to the CRM ticket.
There are two methods to send custom data:
Secure method: predefined data signing with JWT.
Non-secure method: predefined data with plain JSON (Not recommended).
Using the secure method to send custom data
You have to implement signing method. First, you can put your custom data on client side, and send to your server to sign it. On your server you can add additional data by defined form and sign with your company.secret and return it by 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];
}
}
Using insecure method to send custom data
This method is not recommended as it creates a potential vulnerability
which could open your application to a man-in-the-middle attack. If you
choose to use this method, we are not responsible for the security
exposure and potential damage which may occur. We encourage you to use
the secure method described above to send custom data in your
application. Or you can just start the iOS SDK with UJETCustomData
instance. In this case, signPayload
delegate for UJETPayloadCustomData
should just call 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;
[CCAI Platform startWithOptions:options];
Using unsigned custom data to send external chat transcript
You can send the chat transcript to the SDK when it's started with unsigned custom data by calling setExternalChatTransfer: or setExternalChatTransferWithDictionary: method to set JSON data with NSString or NSDictionary, respectively.
UJETCustomData *customData = [UJETCustomData new];
[customData setExternalChatTransfer:jsonString];
UJETStartOptions *options = [UJETStartOptions new];
options.unsignedCustomData = customData;
[CCAI Platform startWithOptions:options];
JSON format:
greeting_override: string
agent: dictionary
name: string
avatar: string [url of agent avatar, optional]
transcript: array
sender: string ["end_user" or "agent"]
timestamp: string [ie "2021-03-15 12:00:00Z"]
content: array
type: string [one of text, media]
text: string [required for text type]
media: dictionary [required for media type]
type: string [one of image, video]
url: string [public url pointing at media file]
JSON example:
{
"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"
}
]
}
]
}
You can use markdown on the text type. Supported syntax include italic,
bold, bullet list, hyperlink, and underline (--text--
).
Example of custom data { :#example-of-custom-data }
JSON encoded to JWT
The JSON file should validate JWT. And the object of custom data is value of custom_data key.
{
"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"
}
}
}
Each data is similar to JSON object format and should contain the key, value, type, and label.
The key is unique identifier for the data. label is display name on CRM's page type is type of the value.
string
- JSON string
number
- integer, float
date
- UTC Unix timestamp format with 13 digits. (contains milliseconds)
url
- HTTP url format
CRM example
Location
Use CoreLocation framework. For more details please refer to AppDelegate.m
.
Device OS Version
[customData set:@"os_version" label:@"OS Version" stringValue:[[UIDevice currentDevice] systemVersion]];
Prevent the display of custom data
You can use the invisible_to_agent
property with a custom data object to
prevent signed or unsigned custom data from being displayed in the agent
adapter. In the previous example, the end-user's social security number is not
shown in the agent adapter because "invisible_to_agent" : true
is included in
the ssn
object.
When you include the "invisible_to_agent" : true
property with a custom data object,
you can expect the following behavior:
- The custom data is included in the session metadata file.
- The custom data is not included in CRM records.
For more information, see View session data in the agent adapter.
Reserved data properties
You can send reserved data properties to CCAI Platform as signed custom data when a session begins. For more information, see Send reserved data properties.
The following is an example of reserved data properties in custom data:
{ "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" } } }
Replace the following:
VERIFIED_CUSTOMER_BOOLEAN
: True if you consider this end-user to be a legitimate customer.VERIFIED_BAD_ACTOR_BOOLEAN
: True if you consider this end-user to potentially be a bad actor.REPEAT_CUSTOMER_BOOLEAN
: True if you have determined that this end-user has contacted your contact center before.
Customize Flow
Disconnect CCAI Platform for handling Host app events
// CCAI Platform is connected
...
// An event has come
[CCAI Platform disconnect:^{
// Handle an event
}];
Postpone CCAI Platform incoming call or chat
Implement a delegate method for handling incoming events
- (BOOL)shouldConnectUjetIncoming:(NSString *)identifier forType:(UjetIncomingType)type {
if (weDoingSomething) {
// save identifier and type
return NO; // postpone
} else {
return YES;
}
}
Connect postponed event
[CCAI Platform connect:identifier forType:UjetIncomingTypeCall];
Setup Deep link
This enables agents on PSTN calls to use smart actions by SMS for both when an end user has or does not have the app.
Go to Settings > Operation Management > Enable Send SMS to Download App in the CCAI Platform portal.
You can set App URL with your web page (i.e, https://your-company.com/support) after configuring Universal Link or custom URL scheme. You can select either way.
Implement delegate method to handle deep link
The universal link and custom URL look like https://your-company.com/support?call_id=xxx&nonce=yyy and your-company://support?call_id=xxx&nonce=yyy respectively. Put one of your links without query parameters under App URL in Admin Portal. For example, put your-company://support if using a custom URL scheme.
In the delegate method, make sure to only call [CCAI Platform start] when the URL paths and parameters in the universal link or custom URL are specific for CCAI Platform.
- (BOOL)application:(UIApplication *)app continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler {
...
if ([NSUserActivityTypeBrowsingWeb isEqualToString:userActivity.activityType]) {
NSURL *url = userActivity.webpageURL;
NSArray *availableSchema = @[
@"your-company", // custom URL scheme
@"https" // universal link
];
NSArray *availableHostAndPath = @[
@"ujet", // custom URL scheme
@"your-comany.com/ujet" // universal link
];
if (![availableSchema containsObject:url.scheme]) {
return NO;
}
NSString *hostAndPath = [NSString stringWithFormat:@"%@%@", url.host, url.path];
if (![availableHostAndPath containsObject:hostAndPath]) {
return NO;
}
// your-company://ujet?call_id={call_id}&nonce={nonce}
// https://your-company.com/ujet?call_id={call_id}&nonce={nonce}
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url
resolvingAgainstBaseURL:NO];
NSArray *queryItems = urlComponents.queryItems;
NSString *callId = [self valueForKey:@"call_id" fromQueryItems:queryItems];
// validate call id
if (![self isValidCallId:callId]) {
return NO;
}
NSString *nonce = [self valueForKey:@"nonce" fromQueryItems:queryItems];
UJETStartOptions *options = [[UJETStartOptions alloc] initWithCallId:callId nonce:nonce];
[CCAI Platform startWithOptions:options];
}
...
}
Please see the example codes from UJETObject+DeepLink file.
Observe CCAI Platform event
We post the following events through NSNotificationCenter.defaultCenter. You can listen to them and customize your flow depending on your use case, e.g., custom keyboard layout.
UJETEventEmailDidClick
- Queue Menu Data
UJETEventEmailDidSubmit
Queue Menu Data
has_attachment: (NSNumber) @YES, @NO
UJETEventSessionViewDidAppear
type: @"call", @"chat"
timestamp: (NSString) ISO 8601
UJETEventSessionViewDidDisappear
type: @"call", @"chat"
timestamp: (NSString) ISO 8601
UJETEventSessionDidCreate
- Session Data
UJETEventSessionDidEnd
Session Data
agent_name: (NSString) null if agent didn't join
duration: (NSNumber) only for call
ended_by: (NSString)
type=call: @"agent", @"end_user"
type=chat: @"agent", @"end_user", @"timeout", @"dismissed"
UJETEventSdkDidTerminate
Event Data
Meta Data
application: @"iOS"
app_id: (NSString) bundle identifier
app_version: (NSString)
company: (NSString) subdomain
device_model: (NSString)
device_version: (NSString)
sdk_version: (NSString)
timestamp: (NSString) ISO 8601
Queue Menu Data
Meta Data
menu_id: NSString
menu_key: NSString, nullable
menu_name: NSString
menu_path : NSString
Session Data
Queue Menu Data
session_id: NSString
type: @"call", @"chat"
end_user_identifier: NSString
UI and string customization
There are several options for the SDK theme listed in UJETGlobalTheme.h
`.
Set your theme after `[UJET initialize] for example:
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];
The company name is retrieved from Admin Portal > Settings > Support Center Details > Display Name.
You can also set the logo image instead of the company name. The image will be resized to fit on the area if it is too large:
theme.companyImage = [UIImage imageNamed:@"logo"];
Strings
You can also customize strings by overriding the value. For example, put this
key/value on your Localizable.strings
:
ujet_greeting_title = "Title";
"ujet_greeting_description" = "Description";
Available customizable strings are listed in ujet.strings
file.
Specify dark mode for font legibility
You can specify a tint of the color you want for dark mode to make fonts more legible.
@property (nonatomic, strong) UIColor *tintColorForDarkMode;
If you don't set the property, UJETGlobalTheme.tintColor
` will be used for
dark mode. We recommend setting this property if your app supports dark mode.
The following external documentation provides insights on how to pick the right
tint color for dark mode:
You can control the visibility of the status bar with this property:
@property (nonatomic, assign) BOOL hideStatusBar;
Chat theme
To customize the chat screen, you have the option to use a JSON string or each theme class.
For reference, you can check out the example app and uncomment the
customizeChatTheme
method.
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)
}
Other appearances
You can customize other appearances such as font size and background color:
theme.supportTitleLabelFontSize = 30;
theme.supportDescriptionLabelFontSize = 20;
theme.supportPickerViewFontSize = 30;
theme.staticFontSizeInSupportPickerView = YES;
theme.backgroundColor = UIColor.darkGrayColor;
theme.backgroundColorForDarkMode = UIColor.lightGrayColor;
Content cards
You can add customization for content cards together with chat customization.
You can do this either using the json file (see content_card
property) or by
using the UJETChatContentCardTheme
class.
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)
}
Setup Co-browse
If you want to use Co-browse feature, then integrate
UJETCobrowseKit.xcframework
.
CocoaPods: Add the following subspec to your app target.
ruby
target 'MyApp' do
pod 'UJET'
pod 'UJET/Cobrowse'
end
Carthage: Add the following line on the Cartfile:
binary "https://sdk.ujet.co/ios/UJETKit.json"
SwiftPM: Select UJET
and UJETCobrowse
products and add to your app
target.
And set UJETGlobalOptions.cobrowseKey
property.
swift
let options = UJETGlobal
Options()options.cobrowseKey = cobrowseKey
CCAI Platform.setGlobalOptions(options)
Full Device Screen Sharing (optional)
Full device screen sharing allows your support agents to view screens from applications outside of your own. This is often useful where support agents need to check the state of system settings, or need to see the user navigate between multiple applications. If you do not want this feature, you may skip this section.
Broadcast Extension
The feature requires adding a Broadcast Extension.
Open your Xcode project.
Navigate to File > Target.
Pick Broadcast Upload Extension.
Enter a Name for the target.
Uncheck Include UI Extension.
Create the target, noting its bundle ID.
Change the target SDK of your Broadcast Extension to iOS 12.0 or higher.
Integrate SDK
CocoaPods: Add the following subspec to your extenion target:
ruby
target 'MyApp' do
pod 'UJET'
pod 'UJET/Cobrowse'
end
target 'MyAppExtension' do
pod 'UJET/CobrowseExtension'
end
SwiftPM: Select UJETCobrowseExtension
product and add to your
extension target.
Set up keychain sharing
Your app and the app extension you created above need to share some secrets via the iOS Keychain. They do this using their own Keychain group so they are isolated from the rest of your apps Keychain.
In both your app target and your extension target add a
Keychain Sharing entitlement for the io.cobrowse
keychain group.
Add the bundle ID to your plist
Take the bundle ID of the extension you created above, and add the following entry in your apps Info.plist (Note: not in the extensions Info.plist), replacing the bundle ID below with your own:
xml
<key>CBIOBroadcastExtension</key>
<string>your.app.extension.bundle.ID.here</string>
Implement the extension
Xcode will have added SampleHandler.m
and SampleHandler.h
(or
SampleHander.swift
) files as part of the target you created earlier.
Replace the content of the files with the following:
Swift
sh
swift
import CobrowseIOAppExtension
class SampleHandler: CobrowseIOReplayKitExtension {
}
ObjC
objc// SampleHandler.h
@import CobrowseIOAppExtension;
@interface SampleHandler : CobrowseIOReplayKitExtension
@end// SampleHandler.m
#import "SampleHandler.h"
@implementation SampleHandler
@end
Build and run your app
You're now ready to build and run your app. The full device capability is only available on physical devices, it will not work in the iOS Simulator.
Troubleshooting
App submission rejection
App submission is rejected due to the inclusion of CallKit framework in China territory.
If your app is rejected by Apple for this reason, then just leave a comment as the system is designed to deactivate the CallKit framework for China region on VoIP call. This is effective as of SDK version 0.31.1.
SDK size is too large
When SDK size is too large and hard to track on GitHub
In this article, they offer two choices. It is recommended to use Git lfs.
If you are not using Bitcode then stripping bitcode from binary can be an another option. Run this command under UJETKit.xcframework folder.
xcrun bitcode_strip -r CCAI Platform -o CCAI Platform
dyld: Library not loaded error
Add @executable_path/Frameworks on the Runpath Search Paths from Target > Build Settings > Linking.
App submission on iTunes Connect
Apple might ask the following question while in review process because of enabled Voice over IP background mode:
Can users receive VoIP calls in your app?
Respond Yes to the question.
Alert Notification is unavailable when starting SDK
Check the following:
Use real device not simulator.
Enable Push notifications and Background Modes > Voice over IP capability.
If those things do not help, then try to build with distribution provisioning profile (Ad-hoc or Apple Store).
Test push notification against your test app
Prepare your VoIP certificate and device's device token.
On CCAI Platform portal, refer to the Push Notification Debug section in Settings > Developer Settings menu.
If you have already set the certificate for APNS, you don't have to put your certificate again.
Input your certificate (optional) and check whether sandbox or not (optional) and input your test app push notification device token.