Apple iOS 운영체제용 Contact Center AI Platform (CCAI Platform) 모바일 SDK는 iOS 모바일 애플리케이션 내에 CCAI Platform 모바일 환경을 삽입하는 기능을 제공합니다.
요구사항
iOS 모바일 SDK의 요구사항은 다음과 같습니다.
- iOS 12.0 이상
회사 사용자 인증 정보 가져오기
관리자 사용자 인증 정보를 사용하여 Contact Center AI Platform (CCAI Platform) 포털에 로그인합니다.
설정 > 개발자 설정으로 이동합니다.
회사 키 및 비밀번호에서 회사 키와 회사 비밀번호를 기록합니다.
시작하기
다음은 CCAI Platform iOS 모바일 SDK를 시작하는 방법을 안내합니다.
설치
시작하려면 iOS SDK를 설치해야 합니다.
예시 앱 다운로드
iOS 예시 앱을 다운로드합니다.
폴더로 이동하여 CocoaPods를 사용하여 종속 항목을 설치합니다.
$ pod install --project-directory=ExampleApp프로젝트 설정을 빠르게 구성하려면 셸 스크립트를 실행하세요.
$ ./setup.sh또는 다음 단계에 따라 프로젝트 설정을 수동으로 수정할 수 있습니다.
ExampleApp.xcworkspace를 엽니다.Info.plist의UJETCompanyKey및UJETCompanySecret값을 CCAI Platform 포털의 Settings> Developer Settings 페이지에 있는 Company Key 및 Company Secret Code 값으로 바꿉니다.Info.plist의UJETSubdomain값을 CCAI Platform 포털 URL의 하위 도메인으로 바꿉니다. 하위 도메인이 URL에서.ujet.com바로 앞에 옵니다(예:https://your-subdomain.ujet.com/settings/developer-setting의your-subdomain).
프로젝트에 통합
애플리케이션과의 iOS SDK 통합은 개발 환경에 따라 다릅니다.
Swift 패키지 관리자
iOS SDK용 Swift 패키지를 추가합니다.
빌드 설정의 기타 링커 플래그에 -ObjC를 입력합니다.
최신 버전의 Xcode (현재 13.2)에는 Swift Package Manager를 사용하여 배포된 바이너리 프레임워크를 사용하는 데 알려진 문제가 있습니다. 이 문제의 현재 해결 방법은 Xcode 프로젝트의 빌드 단계에 실행 스크립트 단계를 추가하는 것입니다. 이 Run Script Phase는 Embed Frameworks 빌드 단계 다음에 와야 합니다. 이 새 스크립트 실행 단계에는 다음 코드가 포함되어야 합니다.
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
Google Cloud 에서는 종속 항목 관리자 또는 수동 통합을 사용하는 것이 좋습니다. CCAI 플랫폼 종속 항목은 Carthage를 지원하지 않기 때문입니다. 이렇게 하려면 다음 줄을 추가합니다.
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를 프레임워크, 라이브러리, 삽입된 콘텐츠 섹션으로 드래그하여 타겟에 추가합니다.
1단계의 모든 종속 항목에 대해 2단계와 3단계를 반복합니다.
빌드 설정에서
-ObjC를Other Linker Flags에 넣습니다.타겟의
Linked Frameworks섹션에libc++.tbd를 종속 항목으로 추가합니다.
예제 프로젝트를 사용하여 SDK를 수동으로 빌드하려면 다음 섹션의 단계를 따르세요.
예제 프로젝트를 사용하여 SDK를 수동으로 빌드
다음 단계를 순서대로 따르세요.
UJETKit.xcframework및 기타 종속 항목을 포함한 모든 프레임워크를 다운로드합니다.프로젝트 루트에 CCAI Platform 폴더를 만들고 모든 프레임워크를 추출합니다.
Objc-Manual또는Swift-Manual타겟을 선택하고 빌드합니다.
프레임워크 가져오기
다음 섹션에서는 프레임워크를 가져오는 방법을 설명합니다.
Objective-C 프로젝트
@import UJETKit;
Swift 프로젝트
swiftimport
UJETimport UJETKit
SDK 초기화
UJET_COMPANY_KEY 및 UJET_SUBDOMAIN로 CCAI Platform을 초기화합니다.
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를 최종 사용자 인증 토큰으로 교환합니다. 성공 또는 실패 블록은 대리자가 반환되기 전에 호출되어야 합니다.
익명 사용자 (식별자 = nil)의 경우 애플리케이션이 사용자의 UUID를 만듭니다. 나중에 사용자가 식별자로 인증되면 애플리케이션은 UUID를 기반으로 두 사용자를 병합하려고 시도합니다.
예시 프로젝트의 UJETObject.h:
@import UJETKit;
@interface UJETObject : NSObject <UJETDelegate>
signPayload: payloadType: success: failure: delegate 메서드를 구현합니다.
- (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 최종 사용자 인증을 참고하세요.
푸시 알림 설정
애플리케이션은 인증 및 사진과 같은 스마트 작업을 요청하고 수신 전화를 신고하기 위해 푸시 알림을 전송합니다. 애플리케이션에서는 관리 포털에 저장된 두 가지 유형의 인증서 (VoIP 및 APNs)가 필요합니다.
VoIP 서비스 인증서 준비
Apple의 VoIP 푸시 알림에 대한 참조 문서를 확인할 수 있습니다.
Apple 개발자 사이트에서 VoIP 인증서를 만들고 다운로드합니다.
인증서를 더블클릭하여 키체인에 추가합니다.
Mac에서 Keychain Access 애플리케이션을 시작합니다.
왼쪽 사이드바에서 내 인증서 카테고리를 선택합니다.
VoIP 서비스: 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 플랫폼 포털에 로그인하고 설정 > 개발자 설정 > 모바일 앱으로 이동합니다.
'VoIP 서비스 인증서' 섹션에 인증서를 입력하고 저장합니다. 인증서와 비공개 키 모두에 경계 (
-----BEGIN-----및-----END-----)가 포함되어야 합니다.Xcode에서 디버깅하는 등 개발 프로비저닝 프로필로 앱을 실행하는 경우 샌드박스 체크박스를 선택합니다. 앱이 임시 또는 App Store용으로 보관되고 배포 프로비저닝 프로필을 사용하는 경우 샌드박스 체크박스를 선택 해제합니다.
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];
}
}
푸시 알림 사용 설정
타겟을 선택하고 기능 탭을 엽니다.
푸시 알림 스위치를 사용 설정합니다.
푸시 알림 테스트
다음 섹션에서는 푸시 알림을 테스트하는 방법을 안내합니다.
푸시 알림 디버그 섹션
관리자 포털에서 설정 > 개발자 설정으로 이동합니다. 이 페이지에서 푸시 알림 디버그 섹션을 찾습니다.

오른쪽 텍스트 영역에 기기 토큰을 복사하여 붙여넣고 올바른 모바일 앱을 선택합니다.
기기 토큰 가져오기
기기 토큰 문자열의 예는 다음과 같습니다.
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]);
}
Result
인증서 PEM 파일과 기기 토큰을 입력한 후 버튼을 클릭합니다.
테스트 푸시 알림이 성공적으로 전송되면 결과에 푸시 알림이 성공적으로 구성됨이라는 메시지가 표시됩니다.
기기의 네트워크 연결에 따라 푸시 알림이 전송되지 않을 수도 있습니다.
프로젝트 구성
다음 섹션에서는 프로젝트를 구성하는 데 필요한 변경사항을 간략하게 설명합니다.
기능
타겟 설정에서 다음 기능을 사용 설정합니다.
푸시 알림
백그라운드 모드 (다음 항목 확인)
오디오 및 AirPlay
VoIP
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 플랫폼 포털에 로그인합니다.
설정 > 대기열로 이동합니다.
메뉴 구조에서 대기열을 선택합니다.
직접 액세스 포인트 만들기를 선택합니다.
텍스트 형식으로 키를 입력합니다.
저장을 클릭합니다.

사용자 데이터가 업데이트된 경우 로컬에서 캐시 지우기
호스트 앱에서 페이로드에 서명하는 요청을 재사용하고 빈도를 줄이기 위해 인증 토큰을 키체인에 캐시합니다. 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를 사용하여 폼 데이터를 SDK에FormDataEvent사전으로 전달합니다.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 플랫폼 지원 세션을 시작하려면 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 아이콘이 표시될 수 있습니다. 'icon-call-kit'이라는 이름의 이미지를 Xcassets에 배치합니다.
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 대체
다음과 같은 여러 상황에서 PSTN 대체가 제공됩니다.
모바일 네트워크가 오프라인 상태입니다.
애플리케이션 백엔드에 연결할 수 없습니다.
VoIP를 사용할 수 없음
네트워크 상태가 연결하기에 충분하지 않습니다. 자세한 내용은 UJETGlobalOptions.pstnFallbackSensitivity 속성을 참고하세요.
방화벽 구성 또는 제공업체 문제로 인해 연결 중에 오류가 발생했습니다.
UJETGlobalOptions.fallbackPhoneNumber에 회사 IVR 번호를 설정하는 것이 좋습니다. 권장 형식은 + 다음에 국가 코드와 전화번호가 오는 형식입니다. 예: +18001112222
PSTN 대체 민감도
PSTN 대체에 대한 네트워크 상태 확인의 민감도 수준을 조정할 수 있습니다.
@property (nonatomic, assign) float pstnFallbackSensitivity;
값은 0.0에서 1.0 사이여야 합니다. 1로 설정하면 통화가 VoIP가 아닌 PSTN을 통해 항상 연결됩니다. 최대 지연 시간과 최소 대역폭 임계값은 값이 0인 경우 각각 10000ms와 10KB/s입니다. 예를 들어 값이 0.5이면 최소 지연 시간과 대역폭이 각각 5000ms와 15KB/s임을 의미합니다.
이 값은 다음 단계에 따라 구성할 수 있습니다.
관리자로 CCAI Platform 포털에 로그인합니다.
설정 > 개발자 설정 > 모바일 앱으로 이동합니다.
대체 전화번호 기준 섹션을 찾습니다. 기본값은 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
활동 표시기 맞춤설정
SDK에 자체 로더 애니메이션 (UIView 내)을 추가하고 기본 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 제공업체를 통해 연결되는 통화를 예상했지만 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 티켓에 맞춤 데이터를 전송할 수 있습니다.
맞춤 데이터를 전송하는 방법에는 두 가지가 있습니다.
보안 방법: 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];
}
}
안전하지 않은 방법을 사용하여 맞춤 데이터를 전송함
이 방법은 애플리케이션이 중간자 공격에 취약해질 수 있는 잠재적 취약점을 생성하므로 권장되지 않습니다. 이 방법을 사용하는 경우 발생할 수 있는 보안 노출 및 잠재적 손상에 대해 Google은 책임을 지지 않습니다. 앞서 설명한 보안 방법을 사용하여 애플리케이션에서 맞춤 데이터를 전송하는 것이 좋습니다. 또는 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: 메서드를 호출하여 채팅 스크립트를 SDK에 전송할 수 있습니다. 각각 NSString 또는 NSDictionary로 JSON 데이터를 설정합니다.
UJETCustomData *customData = [UJETCustomData new];
[customData setExternalChatTransfer:jsonString];
UJETStartOptions *options = [UJETStartOptions new];
options.unsignedCustomData = customData;
[UJET startWithOptions:options];
JSON 형식:
greeting_override: string
agent: dictionary
name: string
avatar: string [상담사 아바타의 URL, 선택사항]
transcript: array
sender: string ["end_user" or "agent"]
timestamp: string [예: '2021-03-15 12:00:00Z']
content: array
type: string [text, media 중 하나]
text: string [텍스트 유형에 필요]
media: dictionary [미디어 유형에 필요]
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"
}
]
}
]
}
텍스트 유형에 마크다운을 사용할 수 있습니다. 지원되는 문법에는 기울임꼴, 굵은 글씨체, 글머리 기호 목록, 하이퍼링크, 밑줄 (--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 문자열
숫자
- 정수, 부동 소수점
날짜
- 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 Platform (CCAI Platform)에 전송할 수 있습니다. 자세한 내용은 예약된 데이터 속성 전송을 참고하세요.
다음은 맞춤 데이터의 예약된 데이터 속성의 예입니다.
{
"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 Platform 연결 해제
// CCAI Platform is connected
...
// An event has come
[UJET disconnect:^{
// Handle an event
}];
CCAI Platform 수신 전화 또는 채팅 연기
수신 이벤트를 처리하는 대리자 메서드 구현
- (BOOL)shouldConnectUjetIncoming:(NSString *)identifier forType:(UjetIncomingType)type {
if (weDoingSomething) {
// save identifier and type
return NO; // postpone
} else {
return YES;
}
}
연기된 이벤트 연결
[UJET connect:identifier forType:UjetIncomingTypeCall];
딥 링크 설정
이를 통해 PSTN 통화의 상담사는 최종 사용자가 앱을 보유한 경우와 보유하지 않은 경우 모두 SMS를 통해 스마트 작업을 사용할 수 있습니다.
CCAI 플랫폼 포털에서 설정 > 운영 관리 > 앱 다운로드용 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 플랫폼에만 해당하는 경우에만 [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 Platform 이벤트 관찰
다음 이벤트는 NSNotificationCenter.defaultCenter를 통해 게시됩니다. 이러한 이벤트를 수신하고 사용 사례(예: 맞춤 키보드 레이아웃)에 따라 흐름을 맞춤설정할 수 있습니다.
UJETEventEmailDidClick
- 대기열 메뉴 데이터
UJETEventEmailDidSubmit
대기열 메뉴 데이터
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) only for call
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) 번들 식별자
app_version: (NSString)
company: (NSString) subdomain
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
세션 데이터
대기열 메뉴 데이터
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에는 두 개의 버튼이 있어야 합니다. 하나는 수락이고 다른 하나는 거부입니다.
동의를 얻은 후 클로저 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 프로젝트를 엽니다.
파일 > 타겟으로 이동합니다.
방송 업로드 확장 프로그램을 선택합니다.
타겟의 이름을 입력합니다.
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 키체인 그룹의 키체인 공유 권한을 추가합니다.
번들 ID를 plist에 추가
이전에 만든 확장 프로그램의 번들 ID를 가져와 앱의 Info.plist에 다음 항목을 추가합니다 (참고: 확장 프로그램의 Info.plist가 아님). 다음 번들 ID를 자체 번들 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에서 추적하기 어려운 경우
이 문서에서는 두 가지 선택사항을 제공합니다. Git lfs를 사용하는 것이 좋습니다.
비트코드를 사용하지 않는 경우 바이너리에서 비트코드를 삭제하는 것이 또 다른 옵션이 될 수 있습니다. UJETKit.xcframework 폴더에서 다음 명령어를 실행합니다.
xcrun bitcode_strip -r UJET -o UJET
dyld: 라이브러리가 로드되지 않음 오류
타겟 > 빌드 설정 > 연결에서 실행 경로 검색 경로에 @executable_path/Frameworks를 추가합니다.
iTunes Connect에서 앱 제출
Apple은 VoIP 백그라운드 모드가 사용 설정되어 있기 때문에 검토 과정에서 다음 질문을 할 수 있습니다.
사용자가 앱에서 VoIP 전화를 받을 수 있나요?
질문에 예라고 대답합니다.
SDK 시작 시 알림 알림을 사용할 수 없음
다음을 확인하세요.
시뮬레이터가 아닌 실제 기기를 사용하세요.
푸시 알림 및 백그라운드 모드 > VoIP 기능을 사용 설정합니다.
그래도 문제가 해결되지 않으면 배포 프로비저닝 프로필 (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)
}
}