Apple iOS オペレーティング システム用のコンタクト センター AI プラットフォーム(CCAI プラットフォーム)モバイル SDK を使用すると、CCAI プラットフォームのモバイル エクスペリエンスを iOS モバイル アプリケーションに埋め込むことができます。
要件
iOS モバイル SDK には次の要件があります。
- iOS 12.0 以降
会社の認証情報を取得する
管理者認証情報を使用して、コンタクト センター AI プラットフォーム(CCAI プラットフォーム)ポータルにログインします。
[設定> 開発者向け設定] に移動します。
[Company Key](会社キー)と [Secret Code](シークレット コード)で、会社キーと会社シークレット コードをメモします。
ご利用にあたって
以下では、CCAI Platform iOS モバイル SDK の使用を開始する方法について説明します。
インストール
まず、iOS SDK をインストールする必要があります。
サンプルアプリをダウンロードする
iOS サンプルアプリをダウンロードします。
フォルダに移動し、CocoaPods を使用して依存関係をインストールします。
$ pod install --project-directory=ExampleAppプロジェクト設定をすばやく構成するには、シェル スクリプトを実行します。
$ ./setup.shまたは、次の手順でプロジェクト設定を手動で編集することもできます。
ExampleApp.xcworkspaceを開きます。Info.plistのUJETCompanyKeyとUJETCompanySecretの値を、CCAI Platform ポータルの [設定>デベロッパー設定] ページにある [会社キー] と [会社シークレット コード] の値に置き換えます。Info.plistのUJETSubdomainの値を、CCAI プラットフォーム ポータルの URL のサブドメインに置き換えます。サブドメインは URL の.ujet.comの直前にあります(例:https://your-subdomain.ujet.com/settings/developer-settingのyour-subdomain)。
プロジェクトに統合する
iOS SDK とアプリの統合は、開発環境によって異なります。
Swift Package Manager
iOS SDK 用 Swift パッケージを追加します。
ビルド設定で、[Other Linker Flags] に -ObjC を追加します。
Xcode の最新リリース(現時点では 13.2)では、Swift Package Manager を使用して配布されたバイナリ フレームワークの利用に関する既知の問題があります。この問題の現在の回避策は、Xcode プロジェクトのビルドフェーズに実行スクリプト フェーズを追加することです。この Run Script Phase は、Embed Frameworks ビルドフェーズの後に実行する必要があります。この新しい Run Script Phase には、次のコードが含まれている必要があります。
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
Podfile に次の行を追加します。
pod 'UJET', :podspec => 'https://sdk.ujet.co/ios/x.y.z/ujet.podspec' #specific version x.y.zpod install を実行します。iOS SDK がすでに統合されている場合は、代わりに pod update CCAI Platform を実行します。
Carthage
CCAI Platform の依存関係は Carthage をサポートしていないため、Google Cloud は依存関係マネージャーまたは手動統合の使用を推奨しています。これを行うには、次の行を追加します。
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
手動による統合
これはサポートされていません: https://github.com/twilio/conversations-ios/issues/12。
バイナリ https://raw.githubusercontent.com/twilio/conversations-ios/master/twilio-convo-ios.json
carthage bootstrap --use-xcframeworks(またはcarthage update --use-xcframeworks(依存関係を更新する場合))を実行します。UJETKit.xcframework、UJETFoundationKit.xcframework、UJETChatRedKit.xcframework、UJETChatBlueKit.xcframework、UJETTwilioCallKit.xcframework、およびすべての依存関係TwilioVoice.xcframeworkとTwilioConversationsClient.xcframeworkをダウンロードします。UJETKit.xcframework を [Frameworks, Libraries, and Embedded Content] セクションにドラッグして、ターゲットに追加します。
ステップ 1 のすべての依存関係でステップ 2 と 3 を繰り返します。
[ビルド設定] で、
-ObjCをOther Linker Flagsに配置します。ターゲットの
Linked Frameworksセクションにlibc++.tbdを依存関係として追加します。
サンプル プロジェクトを使用して SDK を手動でビルドする場合は、次のセクションの手順に沿って操作します。
サンプル プロジェクトを使用して SDK を手動でビルドする
次の手順に沿って操作します。
UJETKit.xcframeworkやその他の依存関係を含むすべてのフレームワークをダウンロードします。プロジェクトのルートに CCAI プラットフォーム フォルダを作成し、すべてのフレームワークを抽出します。
Objc-ManualまたはSwift-Manualターゲットを選択してビルドします。
インポート フレームワーク
以降のセクションでは、フレームワークをインポートする手順について説明します。
Objective-C プロジェクト
@import UJETKit;
Swift プロジェクト
swiftimport
UJETimport UJETKit
SDK を初期化する
UJET_COMPANY_KEY と UJET_SUBDOMAIN を使用して CCAI プラットフォームを初期化します。
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;
}
ログレベルを verbose から error に変更できます。デフォルトのログレベルは UjetLogLevelInfo です。
[UJET.setLogLevel:UjetLogLevelVerbose];
エンドユーザー認証
iOS アプリから iOS SDK にアクセスします。
エンドユーザーが認証されていることを確認するため、JWT 署名メカニズムを導入します。
認証が必要な場合、iOS SDK はペイロードの署名を求めます。署名が成功すると、アプリケーションは署名付き JWT をエンドユーザー認証トークンと交換します。成功または失敗のブロックは、デリゲートが戻る前に呼び出す必要があります。
匿名ユーザー(identifier = nil)の場合、アプリはユーザーの UUID を作成します。後でユーザーが識別子で認証されると、アプリは UUID に基づいて 2 人のユーザーの統合を試みます。
サンプル プロジェクトの UJETObject.h:
@import UJETKit;
@interface UJETObject : NSObject <UJETDelegate>
signPayload: payloadType: success: failure: デリゲート メソッドを実装します。
- (void)signPayload:(NSDictionary *)payload payloadType:(UjetPayloadType)payloadType success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure {
if (payloadType == UjetPayloadAuthToken) {
[self signAuthTokenInLocal:payload success:success failure:failure];
}
}
- (void)signAuthTokenInLocal:(NSDictionary *)payload success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure {
NSMutableDictionary *payloadData = [payload mutableCopy];
NSDictionary *userData = [[NSUserDefaults standardUserDefaults] objectForKey:@"user-data"];
[payloadData addEntriesFromDictionary:userData];
payloadData[@"iat"] = [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]]; // required
payloadData[@"exp"] = [NSNumber numberWithDouble:([[NSDate date] timeIntervalSince1970] + 600)]; // required
NSString *signedToken = [self encodeJWT:payloadData];
if (signedToken.length > 0) {
success(signedToken);
} else {
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Failed to sign token" };
NSError *error = [NSError errorWithDomain:@"ExampleApp" code:0 userInfo:userInfo];
failure(error);
}
}
- (NSString *)encodeJWT:(NSDictionary *)payload {
id<JWTAlgorithm> algorithm = [JWTAlgorithmHSBase algorithm384];
NSString *secret = NSBundle.mainBundle.infoDictionary[@"UJETCompanySecret"];
return [JWTBuilder encodePayload:payload].secret().algorithm(algorithm).encode;
}
ペイロードの署名はクライアントではなくアプリケーション サーバーで行うことを強くおすすめします。
この例では、テスト目的でローカル署名を使用しています。UJETObject.m ファイルの signDataInRemote: success: failure: をご覧ください。
詳細については、SDK エンドユーザー認証をご覧ください。
プッシュ通知を設定する
このアプリは、着信の報告だけでなく、確認や写真などのスマート アクションをリクエストするプッシュ通知も送信します。このアプリケーションでは、2 種類の証明書(VoIP と APNs)を管理ポータルに保存する必要があります。
VoIP サービスの証明書を準備する
Apple の VoIP プッシュ通知のリファレンス ドキュメントが用意されています。
Apple Developer サイトから VoIP 証明書を作成してダウンロードします。
証明書をダブルクリックして、キーチェーンに追加します。
Mac でキーチェーン アクセス アプリケーションを起動します。
左側のサイドバーで [My Certificates] カテゴリを選択します。
[VoIP Services: your.app.id] 証明書を右クリックします。
ポップアップ メニューで [エクスポート] を選択します。
パスワードを空白のままにして、パスワードで保護せずに cert.p12 として保存します。
ターミナルで次のコマンドを実行します。
openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -debug -showcertcert.pem の上部は証明書、下部は秘密鍵です。
証明書が Apple のプッシュ通知サーバーで動作していることを確認します。
openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -debug -showcerts成功すると、次の結果が返されます。
--- 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) ---管理者認証情報で CCAI Platform ポータルにログインし、[設定] > [デベロッパー設定] > [モバイルアプリ] に移動します。
[VoIP Services Certificate] セクションに証明書を入力して保存します。証明書と秘密鍵の両方の境界(
-----BEGIN-----と-----END-----)を含めるようにしてください。Xcode でのデバッグなど、開発プロビジョニング プロファイルでアプリを実行している場合は、[サンドボックス] チェックボックスをオンにします。アプリが Ad hoc または App Store 向けにアーカイブされ、配布プロビジョニング プロファイルを使用している場合は、[Sandbox] チェックボックスをオフにします。
Apple プッシュ通知サービスの SSL を準備する
このプロセスは、VOIP サービス証明書のプロセスと似ています。この場合、Apple プッシュ通知サービス SSL(サンドボックスと本番環境)証明書が使用されます。証明書の作成方法については、Apple リモート通知サーバーのドキュメントをご覧ください。
プッシュ通知の統合
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];
UIApplicationDelegate プロトコル ファイルの実装に次のデリゲート メソッドを追加します。
プッシュ通知をテストするには、デバイス トークンを出力してください。
// 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];
}
}
プッシュ通知を有効にする
ターゲットを選択し、[機能] タブを開きます。
[Push Notifications] のスイッチをオンにします。
プッシュ通知をテストする
以降のセクションでは、プッシュ通知をテストする方法について説明します。
プッシュ通知のデバッグ セクション
管理者ポータルで、[設定] > [デベロッパー設定] に移動します。このページで、[プッシュ通知のデバッグ] というセクションを見つけます。

デバイス トークンをコピーして右側のテキスト エリアに貼り付け、正しいモバイルアプリを選択します。
デバイス トークンを取得する
デバイス トークン文字列の例を次に示します。
7db0bc0044c8a203ed87cdab86a597a2c43bf16d82dae70e8d560e88253364b7
プッシュ通知は通常、UIApplicationDelegate または PKPushRegistryDelegate プロトコルに準拠するクラスで設定されます。デバイス トークンは、ある時点で利用可能になります。iOS SDK に渡す前に印刷できます。デバイス トークンを取得するには、コード スニペットを使用します。
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]);
}
結果
証明書 PEM ファイルとデバイス トークンを入力したら、ボタンをクリックします。
テスト プッシュ通知が正常に配信されると、結果に「プッシュ通知が正常に設定されました」というメッセージが表示されます。
プッシュ通知は、デバイスのネットワーク接続によっては、100% 配信されるとは限りません。
プロジェクト構成
以降のセクションでは、プロジェクトの構成に必要な変更について説明します。
機能
ターゲット設定で、次の機能を有効にします。
プッシュ通知
バックグラウンド モード(以下の項目を確認)
オーディオと AirPlay
Voice over IP
Info.plist
ユーザーのプライバシーを保護するため、iOS 10.0 以降でリンクされ、デバイスのマイク、フォト ライブラリ、カメラのいずれかにアクセスする iOS アプリは、その意図を宣言する必要があります。アプリの Info.plist ファイルに次のキーと文字列値を含め、このキーの目的文字列を指定してください。アプリが、対応する目的文字列なしでデバイスのマイク、フォト ライブラリ、カメラにアクセスしようとすると、アプリは終了します。
NSMicrophoneUsageDescription: 通話、サポートチームやトラブルシューティング チームとの会話、製品に関するお問い合わせに関連する音声付き動画の送信のために、マイクへのアクセスを許可します。
NSCameraUsageDescription: お客様がカスタマー サポートのお問い合わせに関連する写真を撮影して送信するために、カメラへのアクセスを許可します。
NSPhotoLibraryUsageDescription: お客様がカスタマー サポートのお問い合わせに関連する写真を送信できるようにします。
NSFaceIDUsageDescription: Face ID を使用した確認へのアクセスを許可します。
iOS SDK を開始する
iOS SDK を開始する場所に次の行を追加します。
[UJET startWithOptions:nil];
このキーを使用して、メニューの特定のポイントから iOS SDK を開始することもできます。
UJETStartOptions *option = [[UJETStartOptions alloc] initWithMenuKey:@"MENU_KEY"];
[UJET startWithOptions:option];
menuKey は、直接アクセス ポイント(DAP)を作成することで作成できます。DAP を作成する手順は次のとおりです。
管理者認証情報を使用して CCAI Platform ポータルにログインします。
[設定] > [キュー] に移動します。
メニュー構造から任意のキューを選択します。
[直接アクセス ポイントを作成] を選択します。
テキスト形式でキーを入力します。
[保存] をクリックします。

ユーザーデータが更新された場合はローカルのキャッシュをクリアする
認証トークンは Keychain にキャッシュ保存され、再利用されるため、ホストアプリからのペイロード署名リクエストの頻度が減ります。SDK は、トークンの有効期限が切れるか、clearUserData 呼び出しで取り消されるまで、このトークンを使用します。ホストアプリは、ログアウト イベントなど、ユーザー関連のデータが変更または更新されたときに、このキャッシュを取り消す役割を担います。
[UJET clearUserData];
Contact Center AI Platform を開始する前に既存のセッションを確認する
セッションを開始する前に、現在のセッションがないことを確認します。これは、userId が変更された場合に特に重要です。
[UJET getStatus];
既存のセッションがある場合は、セッションを再開するか、アクションをキャンセルするようユーザーに促す必要があります。
if ([UJET getStatus] != UjetStatusNone) {
// Display alert to cancel login or resume existing session
}
カスタマイズ
UJETGlobalTheme.h には、SDK テーマのオプションがいくつかあります。
[UJET initialize] の後にテーマを設定します。例:
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];

会社名は、管理ポータル > [設定] > [サポート センターの詳細] > [表示名] から取得されます。
会社名の代わりにロゴ画像を設定するには、次のようにします。
theme.companyImage = [UIImage imageNamed:@"logo"];

画像が大きすぎる場合は、領域に収まるようにサイズが変更されます。
文字列
値をオーバーライドして文字列をカスタマイズすることもできます。たとえば、Localizable.strings に次のキーと値を設定します。
"ujet_greeting_title" = "Title";
"ujet_greeting_description" = "Description";

カスタマイズ可能な文字列は、ujet.strings ファイルに一覧表示されます。
ダークモード
ダークモードでフォントの視認性を高めるために、目的の色合いを指定できます。
@property (nonatomic, strong) UIColor \*tintColorForDarkMode;
プロパティを設定しない場合、ダークモードには UJETGlobalTheme.tintColor が使用されます。アプリがダークモードをサポートしている場合は、このプロパティを設定することをおすすめします。ダークモードに適したティントカラーの選び方については、以下の Apple の記事を参照してください。
チャットのテーマ
チャット画面をカスタマイズするには、JSON 文字列または各テーマクラスを使用します。
リファレンスについては、サンプルアプリを参照して 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)
}

コンテンツ カードのテーマ
コンテンツ カードのカスタマイズは、チャットのカスタマイズと合わせて追加できます。これを行うには、JSON ファイル(content_card プロパティを参照)を使用するか、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)
}

フォームカードのテーマ
フォームカードのカスタマイズは、チャットのカスタマイズと合わせて追加できます。これを行うには、JSON ファイル(form_card property を参照)を使用するか、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)
}
ウェブフォームの構成
ウェブフォーム機能を構成するには、UJETDelegate プロトコルの ujetWebFormDidReceive メソッドを実装します。このメソッドは、フォーム関連の情報を含むイベント(FormMessageReceivedEvent 辞書)をパラメータとして受け取ります。イベント(FormMessageReceivedEvent)ディクショナリには、次の JSON 構造が含まれます。
{
"type": "form_message_received",
"smart_action_id": 1,
"external_form_id": "external_foobar"
"signature": "4868a7e1dcb5..."
}
イベントを処理する手順は次のとおりです。
イベント ディクショナリ(
smart_action_id、external_form_id、signature)から関連情報を抽出します。フォーム URI とフォームデータの署名を生成します。
completion closureを使用して、フォームデータをFormDataEvent辞書として SDK に渡します。URI/署名の生成中にエラーが発生した場合は、
Errorを使用してcallback.onError()でコールバックを呼び出します。
SDK に渡されるディクショナリ(FormDataEvent)は、次の構造である必要があります。
{
"type": "form_data",
"signature": "4868a7e1dcb5...",
"data": {
"smart_action_id":1,
"external_form_id": "form_id",
"uri":"foobar"
}
}
署名(HMAC-SHA:256)は data を使用して生成し、共有秘密鍵で署名する必要があります。署名を生成する前に、データのオブジェクト キーをアルファベット順に並べ替え、同じ data を SDK に送信する必要があります。
セッション後の転送
チャットのカスタマイズとともに、セッション後の VA のカスタマイズを追加できます。これは、JSON ファイル(post_session プロパティを参照)または UJETChatPostSessionVaTheme クラスを使用して実現できます。枠線の幅は 0 または 1 のみです。セッション後の VA エクスペリエンスを区別しない場合は、containerColor を白に設定し、枠線を 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)
}
チャットの操作メニュー
チャットのカスタマイズと合わせて、チャット アクション メニューのカスタマイズを追加できます。これは、JSON ファイル(form_card プロパティを参照)または 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)
}
その他の出演作
フォントサイズや背景色などの外観をカスタマイズできます。
theme.supportTitleLabelFontSize = 30;
theme.supportDescriptionLabelFontSize = 20;
theme.supportPickerViewFontSize = 30;
theme.staticFontSizeInSupportPickerView = YES;
theme.backgroundColor = UIColor.darkGrayColor;
theme.backgroundColorForDarkMode = UIColor.lightGrayColor;

CallKit
iOS 10.0 以降では、すべての通話で CallKit が有効になっています。
CallKit を使用すると、アプリ内通話が着信したときに通話画面が表示され、通話がスマートフォンの通話履歴に表示されます。
通話履歴から新しい CCAI Platform サポート セッションを開始するには、AppDelegate.m に次のブロックを追加します。
AppDelegate.m:
- (BOOL)application:(UIApplication *)app continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler {
if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) {
// Open app from Call history
[UJET startWithOptions:nil];
}
return YES;
}
CallKit を使用すると、デバイスがロックされているときに着信があると、ロック画面に 40x40 のアイコンを表示できます。Xcassets に「icon-call-kit」という名前の画像を配置します。
SDK を構成する
SDK を開始する前に、いくつかのオプションを設定できます。
詳しくは、UJETGlobalOptions クラスをご覧ください。
UJETGlobalOptions *options = [UJETGlobalOptions new];
options.fallbackPhoneNumber = @"+18001112222";
options.preferredLanguage = @"en";
[UJET setGlobalOptions:options];
文字起こしをダウンロードするボタンの表示と非表示を切り替える
チャット オプション メニューとチャット後の画面で文字起こしをダウンロードするボタンを表示または非表示にするように SDK を構成できます。
次のコードは、文字起こしをダウンロードするボタンを構成する方法を示しています。
typedef NS_OPTIONS(NSUInteger, UJETChatDownloadTranscriptVisibilityOptions) {
UJETChatDownloadTranscriptVisibilityOptionsShowAll = 0,
UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu = 1 << 0,
UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen = 1 << 1,
UJETChatDownloadTranscriptVisibilityOptionsHideAll = UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu | UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen
};
@property (nonatomic, assign) UJETChatDownloadTranscriptVisibilityOptions transcriptVisibilityOptions;
PSTN フォールバック
Google は、次のような状況で PSTN フォールバックを提供しています。
モバイル ネットワークがオフラインです。
アプリケーション バックエンドにアクセスできない。
VoIP は利用できません
ネットワークの状況が接続に適していません。詳細については、UJETGlobalOptions.pstnFallbackSensitivity プロパティをご覧ください。
ファイアウォールの構成またはプロバイダの問題により、接続中に障害が発生しました。
会社の IVR 番号は UJETGlobalOptions.fallbackPhoneNumber に設定することをおすすめします。推奨される形式は、+ の後に国コードと電話番号を続けたものです。例: +18001112222。
PSTN フォールバックの感度
ネットワーク状況のチェックから PSTN フォールバックまでの感度レベルを調整できます。
@property (nonatomic, assign) float pstnFallbackSensitivity;
値は 0.0 ~ 1.0 の範囲で指定する必要があります。1 に設定すると、通話は常に VoIP ではなく PSTN 経由で接続されます。値が 0 の場合、最大レイテンシと最小帯域幅のしきい値はそれぞれ 10,000 ミリ秒と 10 KB/秒です。たとえば、値が 0.5 の場合、最小レイテンシと帯域幅はそれぞれ 5,000 ミリ秒と 15 KB/秒になります。
この値は、次の手順で構成できます。
管理者として CCAI Platform ポータルにログインします。
[設定] > [開発者向けの設定] > [モバイルアプリ] に移動します。
[Fallback phone number threshold] セクションを見つけます。デフォルト値は 0.85 です。
新しいしきい値を指定します。
[保存] をクリックします。
グローバル レベルでプッシュ通知をオフにする
プッシュ通知はグローバル レベルでオフにできます。次のプロパティを false に設定すると、すべてのプッシュ通知の依存関係がバイパスされ、プッシュ通知がエンドユーザーに届かなくなります。
@property (nonatomic, assign) BOOL allowsPushNotifications;
ダークモードを無視する
このプロパティを使用すると、CCAI Platform SDK でダークモードを無視できます。
@property (nonatomic, assign) BOOL ignoreDarkMode;
ステータスバーを非表示にする
このプロパティを使用して、ステータスバーの表示 / 非表示を制御できます。
@property (nonatomic, assign) BOOL hideStatusBar;
デフォルトでは、hideStatusBar は false と visible に設定されています。
CSAT アンケートをスキップする
ユーザーが CSAT アンケートをスキップできるボタンを追加できます。次のコードサンプルは、ボタンを追加する方法を示しています。
let options = UJETGlobalOptions()
options.skipCsat = true
アクティビティ インジケーターをカスタマイズする
独自のローダー アニメーション(UIView 内)を SDK に追加して、デフォルトの UIActivityIndicatorView をオーバーライドできます。UJETDelegate から ujet_activityIndicator メソッドを実装し、カスタマイズされたビューを返します。
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
}
アプリの Info.plist で UIUserInterfaceStyle を Light に設定して、ダークモードを完全に無効にしている場合は、このプロパティを無視できます。
設定言語
CCAI Platform SDK は、次の優先順位で優先言語を決定します。
アプリ内のスプラッシュ画面で選択された言語。
デフォルトの言語が
UJETGlobalOptionsから選択されています。デフォルトの言語はpreferredLanguageプロパティで設定できます。サポートされている言語コードはUJETGlobalOptions.hファイルにあります。アプリでサポートされている場合、デバイスで選択されたデバイスの言語([設定> 一般> 言語と地域])が使用されます。
アプリケーションがデバイスの言語をサポートしていないが、最も近い親方言をサポートしている場合、デバイスの言語に最も近い方言が使用されます。たとえば、デバイスの言語としてキューバ スペイン語が選択されていて、アプリがキューバ スペイン語をサポートしていないが、親言語であるスペイン語をサポートしている場合、スペイン語が使用されます。
デバイスの言語がアプリでサポートされていない場合は、英語が使用されます。
外部のたらい回しリンクのアイコンを設定する
外部の転換リンク チャネルのアイコンをカスタマイズするには、アプリのアセット カタログにアイコンをアップロードします。また、管理ポータルの [設定] > [チャット] > [外部の転換リンク] > [リンクを表示] > [転換リンクを追加] で外部の転換リンクを作成する際に、同じアイコン名を使用するようにしてください。管理ポータルのアイコン名がアプリにアップロードされたアイコンと一致しない場合、SDK はデフォルトのアイコンを使用します。アセット カタログに画像を追加する方法については、こちらのリンクをご覧ください。



フォールバック
didHandleUjetError 関数は、予期しないエラーのフォールバックに使用できます。この関数を使用しない場合や、この関数が false を返す場合は、iOS SDK がエラーを処理します。
次の表に、didHandleUjetError 関数がリッスンするエラーを示します。
| エラーの種類 | エラーコード | 説明 |
|---|---|---|
networkError |
1 | ネットワークを利用できません。 注: チャット セッション、通話セッション、レート画面でネットワークが利用できない場合、このエラーはトリガーされません。 |
authenticationError |
100 | 認証中に予期しないエラーが発生しました。 |
authenticationJwtError |
101 | JWT の検証中に予期しないエラー(解析エラーなど)が発生しました。 |
voipConnectionError |
1000 | VoIP プロバイダへの接続を確立できませんでした。VoIP SDK コールバックがこれを処理します。 |
voipLibraryNotFound |
1001 | システムは VoIP プロバイダ経由で電話が接続されることを想定していますが、プロバイダが見つかりません。この問題は、誤った SDK を統合した場合や、VoIP プロバイダ ライブラリを依存関係に追加しなかった場合に発生することがあります。 |
chatLibraryNotFound |
1100 | システムがチャット ライブラリを見つけられない場合に発生します。これは、間違った SDK を統合した場合や、Twilio チャット ライブラリを依存関係に追加しなかった場合に発生する可能性があります。 |
次のコードサンプルは、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
}
}
カスタムデータを CRM に送信する
カスタムデータを CRM チケットに送信できます。
カスタムデータを送信する方法は 2 つあります。
安全な方法: JWT を使用した事前定義済みのデータ署名。
安全でない方法: プレーン JSON を使用した事前定義データ(推奨されません)。
安全なメソッドを使用してカスタムデータを送信する
署名メソッドを実装する必要があります。まず、カスタムデータをクライアントサイドに配置し、署名のためにサーバーに送信します。サーバーで、定義されたフォームで追加データを追加し、company.secret で署名して、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];
}
}
安全でない方法でカスタムデータを送信する
この方法は、アプリケーションが中間者攻撃を受ける可能性がある脆弱性を生み出すため、おすすめしません。この方法を選択した場合、発生する可能性のあるセキュリティの脆弱性や損害については、弊社は責任を負いません。アプリケーションでカスタムデータを送信するには、前述の安全な方法を使用することをおすすめします。または、UJETCustomData インスタンスで iOS SDK を開始することもできます。この場合、UJETPayloadCustomData の signPayload デリゲートは 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];
署名なしのカスタムデータを使用して外部チャットの文字起こしを送信する
署名なしのカスタムデータでチャットが開始されたときに、setExternalChatTransfer: メソッドまたは setExternalChatTransferWithDictionary: メソッドを呼び出して、それぞれ NSString または NSDictionary で JSON データを設定することで、チャットの文字起こしを SDK に送信できます。
UJETCustomData *customData = [UJETCustomData new];
[customData setExternalChatTransfer:jsonString];
UJETStartOptions *options = [UJETStartOptions new];
options.unsignedCustomData = customData;
[UJET startWithOptions:options];
JSON 形式
greeting_override: string
エージェント: 辞書
name: string
avatar: string [エージェントのアバターの URL、省略可]
音声文字変換結果: 配列
sender: string ["end_user" または "agent"]
timestamp: string [例: "2021-03-15 12:00:00Z"]
content: 配列
type: string [text、media のいずれか]
text: string [text 型の場合は必須]
media: 辞書 [メディアタイプの場合に必須]
type: string [image、video のいずれか]
url: string [メディア ファイルを指す公開 URL]
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"
}
]
}
]
}
テキスト型では Markdown を使用できます。サポートされている構文には、斜体、太字、箇条書き、ハイパーリンク、下線(--text--)などがあります。
カスタムデータの例 { :#example-of-custom-data }
JWT にエンコードされた JSON
JSON ファイルは JWT を検証する必要があります。カスタムデータのオブジェクトは、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"
}
}
}
各データは JSON オブジェクト形式に似ており、キー、値、タイプ、ラベルを含む必要があります。
キーはデータの固有識別子です。ラベルは CRM のページでの表示名です。型は値の型です。
文字列
- JSON文字列
数値
- integer、float
日付
- 13 桁の UTC Unix タイムスタンプ形式。(ミリ秒を含む)
URL
- HTTP URL 形式
CRM の例

場所
CoreLocation フレームワークを使用します。詳しくは、AppDelegate.m をご覧ください。
デバイスの OS バージョン
[customData set:@"os_version" label:@"OS Version" stringValue:[[UIDevice currentDevice] systemVersion]];
カスタム データの表示を防止する
invisible_to_agent プロパティをカスタム データ オブジェクトとともに使用すると、署名付きまたは署名なしのカスタム データがエージェント アダプタに表示されないようにできます。前の例では、"invisible_to_agent" : true が ssn オブジェクトに含まれているため、エンドユーザーの社会保障番号はエージェント アダプタに表示されません。
カスタムデータ オブジェクトに "invisible_to_agent" : true プロパティを含めると、次のような動作が想定されます。
- カスタム データはセッション メタデータ ファイルに含まれます。
- カスタムデータは CRM レコードには含まれません。
詳細については、エージェント アダプタでセッション データを表示するをご覧ください。
予約済みのデータ プロパティ
セッションの開始時に、予約済みのデータ プロパティを署名付きカスタムデータとして Contact Center AI プラットフォーム(CCAI プラットフォーム)に送信できます。詳細については、予約済みデータ プロパティを送信するをご覧ください。
カスタムデータの予約済みデータ プロパティの例を次に示します。
{
"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"
}
}
}
次のように置き換えます。
VERIFIED_CUSTOMER_BOOLEAN: このエンドユーザーが正当な顧客であると見なす場合は true。VERIFIED_BAD_ACTOR_BOOLEAN: このエンドユーザーが不正行為者である可能性があると判断した場合に true。REPEAT_CUSTOMER_BOOLEAN: このエンドユーザーが以前にコンタクト センターに連絡したことがあると判断された場合は true。
フローをカスタマイズする
ホストアプリ イベントを処理するために CCAI プラットフォームの接続を解除する
// CCAI Platform is connected
...
// An event has come
[UJET disconnect:^{
// Handle an event
}];
CCAI プラットフォームの着信またはチャットを延期する
受信イベントを処理するデリゲート メソッドを実装する
- (BOOL)shouldConnectUjetIncoming:(NSString *)identifier forType:(UjetIncomingType)type {
if (weDoingSomething) {
// save identifier and type
return NO; // postpone
} else {
return YES;
}
}
Connect の延期イベント
[UJET connect:identifier forType:UjetIncomingTypeCall];
ディープリンクを設定する
これにより、PSTN 通話のエージェントは、エンドユーザーがアプリをインストールしているかどうかにかかわらず、SMS でスマート アクションを使用できます。
CCAI Platform ポータルで、[設定> オペレーション管理 > アプリをダウンロードするための SMS の送信を有効にする] に移動します。
ユニバーサル リンクまたはカスタム URL スキームを構成した後、ウェブページ(https://your-company.com/support など)でアプリ URL を設定できます。どちらの方法でも選択できます。
ディープリンクを処理するデリゲート メソッドを実装する
ユニバーサル リンクとカスタム URL は、それぞれ https://your-company.com/support?call_id=x&nonce=y と your-company://support?call_id=x&nonce=y のようになります。管理ポータルの [アプリの URL] に、クエリ パラメータを含まないリンクのいずれかを入力します。たとえば、カスタム URL スキームを使用する場合は your-company://support を指定します。
デリゲート メソッドでは、ユニバーサル リンクまたはカスタム URL の URL パスとパラメータが CCAI Platform に固有の場合にのみ [UJET start] を呼び出すようにしてください。
- (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];
}
...
}
アプリで UIWindowSceneDelegate を採用している場合は、次のコード スニペットを追加します。
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: [:])
}
}
詳細については、UJETObject+DeepLink ファイルのコード例をご覧ください。
CCAI プラットフォーム イベントをモニタリングする
次のイベントは NSNotificationCenter.defaultCenter を通じて投稿されます。これらのイベントをリッスンし、ユースケース(カスタム キーボード レイアウトなど)に応じてフローをカスタマイズできます。
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
- セッション データ
UJETEventSessionDidEnd
セッション データ
agent_name: (NSString) エージェントが参加していない場合は null
duration:(NSNumber)通話のみ
ended_by:(NSString)
type=call: @"agent", @"end_user"
type=chat: @"agent", @"end_user", @"timeout", @"dismissed"
UJETEventSdkDidTerminate
UJETEventPostSessionOptInDidSelected
- opt_in_selected: (NSString) @"Yes", @"No"
イベントデータ
メタデータ
application: @"iOS"
app_id:(NSString)バンドル ID
app_version: (NSString)
company: (NSString) サブドメイン
device_model: (NSString)
device_version: (NSString)
sdk_version: (NSString)
timestamp:(NSString)ISO 8601
キュー メニューデータ
メタデータ
menu_id: NSString
menu_key: NSString, nullable
menu_name: NSString
menu_path : NSString
セッション データ
Queue Menu Data
session_id: NSString
type: @"call", @"chat"
end_user_identifier: NSString
画面共有を設定する
画面共有機能を使用する場合は、UJETCobrowseKit.xcframework を統合します。
CocoaPods: アプリ ターゲットに次のサブスペックを追加します。
ruby
target 'MyApp' do
pod 'UJET'
pod 'UJET/Cobrowse'
end
Carthage: Cartfile に次の行を追加します。
binary "https://sdk.ujet.co/ios/UJETKit.json"
SwiftPM: UJET プロダクトと UJETCobrowse プロダクトを選択して、アプリのターゲットに追加します。
また、UJETGlobalOptions.cobrowseKey プロパティを設定します。
swift
let options = UJETGlobal
Options()options.cobrowseKey = cobrowseKey
UJET.setGlobalOptions(options)
デバイス全体の画面共有(省略可)
デバイス画面全体の共有を使用すると、サポート エージェントは自社以外のアプリケーションの画面を表示できます。これは、サポート エージェントがシステム設定の状態を確認する必要がある場合や、ユーザーが複数のアプリケーション間を移動する様子を確認する必要がある場合に役立ちます。この機能が不要な場合は、このセクションをスキップしてください。
画面共有の同意ダイアログをカスタマイズする
画面共有の同意ダイアログをカスタマイズするには、プロバイダ クラスで UJETCobrowseAlertProvider プロトコルを実装する必要があります。この実装では、カスタム UIViewController またはそれぞれのプロトコル メソッドを介して UIViewController を継承するその他のオブジェクトを返します。UIViewController には、承諾ボタンと拒否ボタンの 2 つのボタンが必要です。
同意を取得したら、クロージャ consentStatus を呼び出して SDK に渡します。cobrowseFullDeviceRequestAlert から UIViewController。デリゲートには、タイトル付きの RPSystemBroadcastPickerView(下のサンプルコードを参照)が含まれている必要があります。また、別の拒否ボタンも必要です。拒否ボタンのクリック時に、閉じられたクロージャを呼び出します。
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
}
}
カスタム ビュー コントローラには、同意ステータスを 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)
}
}
}
デバイス全体のリクエスト アラートのカスタム ビュー コントローラには、RPSystemBroadcastPickerView と、dismiss ステータスを 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?()
}
}
}
このプロバイダを次の API を介して SDK に渡すことを忘れないでください。
let provider = CobrowseAlertProvider()
UJET.setCobrowseAlertProvider(provider)
ブロードキャスト拡張機能
この機能を使用するには、ブロードキャスト拡張機能を追加する必要があります。
Xcode プロジェクトを開きます。
[File] > [Target] に移動します。
[Broadcast Upload Extension] を選択します。
ターゲットの名前を入力します。
[UI 拡張機能を含める] チェックボックスをオフにします。
ターゲットを作成し、バンドル ID をメモします。
ブロードキャスト拡張機能のターゲット SDK を iOS 12.0 以降に変更します。
SDK を統合する
CocoaPods: 拡張機能のターゲットに次のサブスペックを追加します。
target 'MyApp' do
pod 'UJET'
pod 'UJET/Cobrowse'
end
target 'MyAppExtension' do
pod 'UJET/CobrowseExtension'
end
SwiftPM を使用している場合は、UJETCobrowseExtension プロダクトを選択して、拡張機能のターゲットに追加します。
キーチェーン共有を設定する
アプリと以前に作成したアプリ拡張機能は、iOS キーチェーンを介して一部のシークレットを共有する必要があります。この処理は独自のキーチェーン グループを使用して行われるため、他のアプリのキーチェーンから分離されます。
アプリ ターゲットと拡張機能ターゲットの両方で、io.cobrowse キーチェーン グループの Keychain Sharing 利用資格を追加します。
バンドル ID を plist に追加する
以前に作成した拡張機能のバンドル ID を取得し、アプリの Info.plist(注: 拡張機能の Info.plist ではありません)に次のエントリを追加します。次のバンドル ID はご自身のものに置き換えてください。
xml
<key>CBIOBroadcastExtension</key>
<string>your.app.extension.bundle.ID.here</string>
拡張機能を実装する
Xcode は、以前に作成したターゲットの一部として SampleHandler.m ファイルと SampleHandler.h ファイル(または SampleHander.swift ファイル)を追加しています。ファイルの内容を次のように置き換えます。
Swift: UJETCobrowseExtension プロダクトを選択し、拡張機能ターゲットに追加します。
import CobrowseIOAppExtension
class SampleHandler: CobrowseIOReplayKitExtension {
}
ObjC
objc// SampleHandler.h
@import CobrowseIOAppExtension;
@interface SampleHandler : CobrowseIOReplayKitExtension
@end// SampleHandler.m
#import "SampleHandler.h"
@implementation SampleHandler
@end
アプリをビルドして実行する
これで、アプリをビルドして実行する準備が整いました。デバイスの全機能は実機でのみ利用可能で、iOS シミュレータでは動作しません。
SDK を最小化する
チャット セッションまたは通話が進行中の場合、Contact Center AI Platform SDK を最小化できます。これは、コンテンツ カードのクリックなど、SDK イベントを受信した後にユーザーをアプリに戻したい場合に便利です。SDK を最小化してユーザーをアプリに戻すには、次の方法を使用します。
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
}
トラブルシューティング
アプリの送信が否承認となった場合
中国地域で CallKit フレームワークが含まれているため、アプリの送信が拒否されました。
この理由でアプリが Apple に不承認とされた場合は、コメントを残してください。システムは、VoIP 通話で中国地域の CallKit フレームワークを無効にするように設計されています。これは SDK バージョン 0.31.1 以降で有効です。
SDK のサイズが大きすぎる
SDK のサイズが大きすぎて GitHub で追跡するのが難しい場合
この記事では、2 つの選択肢が提示されています。Git lfs を使用することをおすすめします。
Bitcode を使用していない場合は、バイナリから Bitcode を削除することもできます。UJETKit.xcframework フォルダで次のコマンドを実行します。
xcrun bitcode_strip -r UJET -o UJET
dyld: Library not loaded エラー
[Target] > [Build Settings] > [Linking] の [Runpath Search Paths] に @executable_path/Frameworks を追加します。
iTunes Connect でのアプリの送信
Voice over IP のバックグラウンド モードが有効になっているため、審査プロセス中に Apple から次の質問が届くことがあります。
ユーザーはアプリで VoIP 通話を受信できますか?
質問に「はい」と回答します。
SDK の起動時にアラート通知が利用できない
以下を確認します。
シミュレータではなく実際のデバイスを使用します。
プッシュ通知とバックグラウンド モード > Voice over IP 機能を有効にします。
それでも解決しない場合は、配布プロビジョニング プロファイル(Ad-hoc または Apple Store)を使用してビルドしてみてください。
テストアプリに対してプッシュ通知をテストする
VoIP 証明書とデバイスのデバイス トークンを準備します。
CCAI Platform ポータルで、[設定> デベロッパー設定] メニューの [プッシュ通知のデバッグ] セクションを参照してください。
APNS の証明書をすでに設定している場合は、証明書を再度設定する必要はありません。
証明書を入力(省略可)し、サンドボックスかどうかを確認(省略可)して、テストアプリのプッシュ通知デバイス トークンを入力します。
新しいチャットの開始に 30 秒以上かかった
カスタムデータのデリゲート メソッドに応答しているかどうかを確認します。リクエストに応じて有効なカスタムデータを返すか、成功ブロックで nil を返す必要があります。
次のコード スニペットを構成例として使用します。
public func signPayload(_ payload: [AnyHashable: Any]?, payloadType: UjetPayloadType, success: (String?) -> Void, failure: (Error?) -> Void)
{
if payloadType == .customData {
success(nil)
}
}