iOS SDK 가이드

Apple iOS 운영체제용 Contact Center AI Platform (CCAI Platform) 모바일 SDK는 iOS 모바일 애플리케이션 내에 CCAI Platform 모바일 환경을 삽입하는 기능을 제공합니다.

요구사항

iOS 모바일 SDK에는 다음과 같은 요구사항이 있습니다.

  • iOS 12.0 이상

회사 사용자 인증 정보 검색

  1. 관리자 인증 정보를 사용하여 Contact Center AI Platform (CCAI Platform) 포털에 로그인합니다.

  2. 설정 > 개발자 설정으로 이동합니다.

  3. Company Key(회사 키) 및 Secret Code(비밀번호)에서 회사 키와 회사 비밀번호를 기록합니다.

시작하기

다음은 CCAI Platform iOS 모바일 SDK를 시작하는 방법에 관한 가이드입니다.

설치

시작하려면 iOS SDK를 설치해야 합니다.

예시 앱 다운로드

  1. iOS 예시 앱을 다운로드합니다.

  2. 폴더로 이동하여 CocoaPods를 사용하여 종속 항목을 설치합니다.

    $ pod install --project-directory=ExampleApp
    
  3. 프로젝트 설정을 빠르게 구성하려면 셸 스크립트를 실행합니다.

    $ ./setup.sh
    

    또는 다음 단계에 따라 프로젝트 설정을 수동으로 수정할 수 있습니다.

    1. ExampleApp.xcworkspace를 엽니다.

    2. Info.plist의 UJETCompanyKey 및 UJETCompanySecret 값을 CCAI Platform 포털의 설정 > 개발자 설정 페이지에 있는 회사 키 및 회사 보안 코드 값으로 바꿉니다.

    3. Info.plist의 UJETSubdomain 값을 CCAI Platform 포털 URL의 하위 도메인으로 바꿉니다. 하위 도메인은 URL에서 .ujet.com 바로 앞에 옵니다(예: https://your-subdomain.ujet.com/settings/developer-setting의 your-subdomain).

프로젝트에 통합

iOS SDK를 애플리케이션과 통합하는 방법은 개발 환경에 따라 다릅니다.

Swift Package Manager

  1. GitHub에서 iOS SDK용 Swift 패키지를 추가합니다.

  2. 빌드 설정에서 Other Linker Flags(기타 링커 플래그)에 -ObjC를 추가합니다.

  3. 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

  1. Podfile에 다음 줄을 추가합니다.

    pod 'UJET', :podspec =>
    'https://sdk.ujet.co/ios/x.y.z/ujet.podspec' #specific version
    x.y.z
    
  2. pod install을 실행합니다. iOS SDK가 이전에 통합된 경우 pod update CCAI Platform을 대신 실행합니다.

Carthage

CCAI Platform 종속 항목은 Carthage를 지원하지 않으므로Google Cloud 에서는 종속 항목 관리자 또는 수동 통합을 사용하는 것이 좋습니다.

다른 종속 항목 관리자를 사용하려면 다음 단계를 따르세요.

  1. 다음 줄을 추가합니다.

    binary "https://sdk.ujet.co/ios/UJETKit.json"
    binary "https://sdk.ujet.co/ios/UJETFoundationKit.json"
    binary "https://sdk.ujet.co/ios/UJETCallRedKit.json"
    binary "https://sdk.ujet.co/ios/UJETChatRedKit.json"
    # optional
    # binary "https://sdk.ujet.co/ios/UJETChatBlueKit.json"
    binary "https://raw.githubusercontent.com/twilio/twilio-voice-ios/Releases/twilio-voice-ios.json"
    # 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"
    
  2. carthage bootstrap --use-xcframeworks를 실행합니다(종속 항목을 업데이트하는 경우 carthage update --use-xcframeworks).

수동 통합
  1. UJETKit.xcframework, UJETFoundationKit.xcframework, UJETChatRedKit.xcframework, UJETChatBlueKit.xcframework, UJETTwilioCallKit.xcframework 및 모든 종속 항목 TwilioVoice.xcframework 및 TwilioConversationsClient.xcframework를 다운로드합니다.

  2. UJETKit.xcframework를 프레임워크, 라이브러리, 삽입된 콘텐츠 섹션으로 드래그하여 타겟에 추가합니다.

  3. 1단계의 모든 종속 항목에 대해 2단계와 3단계를 반복합니다.

  4. 빌드 설정에서 -ObjC를 Other Linker Flags에 배치합니다.

  5. libc++.tbd를 타겟의 Linked Frameworks 섹션에 종속 항목으로 추가합니다.

예시 프로젝트를 사용하여 SDK를 수동으로 빌드하려면 다음 섹션의 단계를 따르세요.

예시 프로젝트를 사용하여 SDK를 수동으로 빌드하기

다음 단계를 순서대로 따르세요.

  1. UJETKit.xcframework 및 기타 종속 항목을 포함한 모든 프레임워크를 다운로드합니다.

  2. 프로젝트 루트에 CCAI Platform 폴더를 만들고 모든 프레임워크를 추출합니다.

  3. 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: 대리자 메서드를 구현합니다.

-   (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 최종 사용자 인증을 참조하세요.

푸시 알림 설정하기

CCAI Platform은 수신 전화를 보고할 뿐만 아니라 인증 및 사진과 같은 스마트 작업을 요청하는 푸시 알림을 전송합니다. 채팅 대기열에서 체크인을 사용하는 경우 (iOS SDK 2.16.0 이상 필요) CCAI Platform은 대기 중인 소비자에게 채팅으로 돌아가 준비가 되었는지 확인하라는 푸시 알림을 보냅니다. 모바일 SDK 체크인을 참고하세요. CCAI Platform에서는 두 가지 유형의 인증서 (VoIP 및 APN)를 CCAI Platform 관리 포털에 저장해야 합니다.

인증서 구성

CCAI Platform에서는 CCAI Platform 관리 포털에 두 가지 유형의 Apple 푸시 인증서를 저장해야 합니다.

  • Apple 푸시 알림 서비스 (APN) SSL 인증서 - 표준 원격 푸시 알림에 사용됩니다.

  • VoIP 서비스 인증서: 수신 전화 알림 및 특정 통화 관련 스마트 작업에 사용됩니다.

두 인증서 모두 Apple 개발자 포털에서 생성되고 PEM 형식으로 변환된 후 CCAI Platform 관리 포털에 업로드됩니다.

APNs SSL 인증서 만들기

Apple의 공식 APN에 인증서 기반 연결 설정 가이드에 따라 다음 단계를 수행하여 APN SSL 인증서를 만듭니다.

  • developer.apple.com에 로그인하고 Certificates, identifiers & profiles(인증서, 식별자, 프로필)로 이동합니다.

  • + 버튼을 클릭하여 새 인증서를 만듭니다.

  • Services(서비스)에서 Apple push notification service SSL (Sandbox & production)(Apple 푸시 알림 서비스 SSL(샌드박스 및 프로덕션))을 선택합니다.

  • 애플리케이션의 번들 식별자와 일치하는 앱 ID를 선택한 다음 계속을 클릭합니다.

  • Mac의 키체인 접근에서 생성된 인증서 서명 요청 (CSR)을 업로드합니다.

    • 키체인 접근 > 인증서 지원 > 인증 기관에서 인증서 요청을 엽니다.

    • 이메일 주소를 입력하고 CA 이메일 주소를 비워두고 디스크에 저장됨을 선택한 후 계속을 클릭합니다.

  • 계속을 클릭한 다음 생성된 .cer 파일 다운로드를 클릭합니다.

  • .cer 파일을 더블클릭하여 키체인에 설치합니다.

  • 키체인 액세스에서 설치된 인증서를 찾아 마우스 오른쪽 버튼으로 클릭하고 내보내기…를 선택하여 .p12 파일로 저장합니다. 내보내기 비밀번호를 설정하라는 메시지가 표시됩니다.

  • openssl를 사용하여 .p12 파일을 PEM 형식으로 변환합니다.

# Extract the certificate
openssl pkcs12 -in apns_certificate.p12 -out apns_cert.pem -clcerts -nokeys
# Extract the private key
openssl pkcs12 -in apns_certificate.p12 -out apns_key.pem -nocerts -nodes
# (Optional) Combine into a single PEM file
cat apns_cert.pem apns_key.pem > apns_combined.pem

VoIP 서비스 인증서 만들기

VoIP 서비스 인증서를 사용하면 앱이 Apple의 PushKit 프레임워크를 통해 우선순위가 높은 푸시 알림을 수신할 수 있으며, 이를 통해 앱이 종료된 상태에서 해제되어 수신 전화를 처리할 수 있습니다. 최신 공식 단계는 Apple 개발자 문서의 VoIP 서비스 인증서 만들기를 참고하세요.

  • developer.apple.com에 로그인하고 Certificates, Identifiers & Profiles(인증서, 식별자, 프로필)로 이동합니다.

  • + 버튼을 클릭하여 새 인증서를 만듭니다.

  • Services(서비스)에서 VoIP services certificate(VoIP 서비스 인증서)를 선택합니다.

  • 애플리케이션의 번들 식별자와 일치하는 앱 ID를 선택한 다음 계속을 클릭합니다.

  • 이전 섹션에서 생성한 것과 동일한 인증서 서명 요청(CSR)을 업로드하거나 새로 만듭니다.

  • 계속을 클릭한 다음 생성된 .cer 파일을 다운로드합니다.

  • .cer 파일을 더블클릭하여 키체인에 설치합니다.

  • 키체인 액세스에서 설치된 VoIP 서비스 인증서를 찾아 마우스 오른쪽 버튼으로 클릭하고 내보내기…를 선택하여 .p12 파일로 저장합니다.

  • openssl를 사용하여 .p12 파일을 PEM 형식으로 변환합니다.

# Extract the certificate
openssl pkcs12 -in voip_certificate.p12 -out voip_cert.pem -clcerts -nokeys
# Extract the private key
openssl pkcs12 -in voip_certificate.p12 -out voip_key.pem -nocerts -nodes
# (Optional) Combine into a single PEM file
cat voip_cert.pem voip_key.pem > voip_combined.pem

CCAI Platform 관리 포털에 인증서 업로드

  • CCAI Platform 관리 포털에 로그인합니다.

  • 설정 > 개발자 설정 > 모바일 앱으로 이동합니다.

  • APNs PEM 인증서를 업로드합니다.

  • VoIP PEM 인증서를 업로드합니다.

푸시 알림 사용 설정

  1. 타겟을 선택하고 Capabilities 탭을 엽니다.

  2. 푸시 알림 스위치를 사용 설정합니다.

푸시 알림 테스트

다음 섹션에서는 푸시 알림을 테스트하는 방법을 안내합니다.

푸시 알림 디버그 섹션

관리자 포털에서 설정 > 개발자 설정으로 이동합니다. 이 페이지에서 푸시 알림 디버그 섹션을 찾습니다.

오른쪽 텍스트 영역에 기기 토큰을 복사하여 붙여넣고 올바른 모바일 앱을 선택합니다.

기기 토큰 가져오기

기기 토큰 문자열의 예는 다음과 같습니다.

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 파일과 기기 토큰을 입력한 후 버튼을 클릭합니다.

테스트 푸시 알림이 전송되면 푸시 알림이 구성됨이라는 메시지가 표시됩니다.

푸시 알림은 기기의 네트워크 연결에 따라 전송이 보장되지 않습니다.

프로젝트 구성

다음 섹션에서는 프로젝트를 구성하는 데 필요한 변경사항을 간략하게 설명합니다.

기능

타겟 설정에서 다음 기능을 사용 설정합니다.

  • 푸시 알림

  • 백그라운드 모드 (다음 항목 선택)

  • 오디오 및 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를 만드는 방법을 안내합니다.

  1. 관리자 인증 정보로 CCAI Platform 포털에 로그인합니다.

  2. 설정 > 대기열로 이동합니다.

  3. 메뉴 구조에서 대기열을 선택합니다.

  4. 직접 액세스 포인트 만들기를 선택합니다.

  5. 텍스트 양식에 키를 입력합니다.

  6. 저장을 클릭합니다.

사용자 데이터가 업데이트된 경우 로컬에서 캐시 지우기

호스트 앱에서 페이로드에 서명하는 요청을 재사용하고 빈도를 줄이기 위해 키체인에 인증 토큰을 캐시합니다. 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..."
  }

이벤트를 처리하려면 다음 단계를 따르세요.

  1. 이벤트 사전(smart_action_id, external_form_id, signature)에서 관련 정보를 추출합니다.

  2. 양식 데이터의 양식 URI 및 서명을 생성합니다.

  3. completion closure를 사용하여 양식 데이터를 FormDataEvent 사전으로 SDK에 전달합니다.

  4. 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만 가능하며 세션 후 가상 에이전트 환경을 구분하지 않으려면 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 아이콘을 표시할 수 있습니다. '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 대체

Google은 다음과 같은 여러 상황에서 PSTN 대체 기능을 제공합니다.

  • 모바일 네트워크가 오프라인 상태입니다.

  • 애플리케이션 백엔드에 연결할 수 없습니다.

  • VoIP를 사용할 수 없음

    • 네트워크 상태가 연결하기에 충분하지 않습니다. 자세한 내용은 UJETGlobalOptions.pstnFallbackSensitivity 속성을 참조하세요.

    • 방화벽 구성 또는 제공업체 문제로 인해 연결 중에 오류가 발생했습니다.

UJETGlobalOptions.fallbackPhoneNumber에 회사 IVR 번호를 설정하는 것이 좋습니다. 권장 형식은 + 다음에 국가 코드와 전화번호가 오는 것입니다. +18001112222.

PSTN 대체 민감도

네트워크 상태를 PSTN 대체로 확인하는 민감도 수준을 조정할 수 있습니다.

@property (nonatomic, assign) float pstnFallbackSensitivity;

값은 0.0에서 1.0 사이여야 합니다. 1로 설정하면 통화가 VoIP가 아닌 PSTN을 통해 항상 연결됩니다. 최대 지연 시간과 최소 대역폭 기준점은 값이 0인 경우 각각 10,000ms 및 10KB/s입니다. 예를 들어 값이 0.5이면 최소 지연 시간과 대역폭이 각각 5,000ms 및 15KB/s입니다.

이 값은 다음 단계에 따라 구성할 수 있습니다.

  1. 관리자로 CCAI Platform 포털에 로그인합니다.

  2. 설정 > 개발자 설정 > 모바일 앱으로 이동합니다.

  3. 대체 전화번호 기준 섹션을 찾습니다. 기본값은 0.85입니다.

  4. 새 임곗값을 지정합니다.

  5. 저장을 클릭합니다.

전체 수준에서 푸시 알림 사용 중지

전체 수준에서 푸시 알림을 사용 중지할 수 있습니다. 다음 속성을 false로 설정하면 모든 푸시 알림 종속 항목이 우회되고 푸시 알림이 최종 사용자에게 도달하지 않습니다.

@property (nonatomic, assign) BOOL allowsPushNotifications;

어두운 모드 무시

이 속성을 사용하면 CCAI Platform SDK에서 어두운 모드를 무시할 수 있습니다.

@property (nonatomic, assign) BOOL ignoreDarkMode;

상태 표시줄 숨기기

이 속성을 사용하여 상태 표시줄의 공개 상태를 제어할 수 있습니다.

  @property (nonatomic, assign) BOOL hideStatusBar;

기본적으로 hideStatusBar는 false 및 visible로 설정됩니다.

고객만족도(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는 다음 우선순위에 따라 기본 언어를 결정합니다.

  1. 앱 내 스플래시 화면에서 선택한 언어입니다.

  2. UJETGlobalOptions에서 선택한 기본 언어입니다. preferredLanguage 속성으로 기본 언어를 설정할 수 있습니다. 지원되는 언어 코드는 UJETGlobalOptions.h 파일에서 확인할 수 있습니다.

  3. 앱에서 지원하는 경우 기기에서 선택한 기기 언어 (설정 > 일반 > 언어 및 지역)가 사용됩니다.

  4. 애플리케이션이 기기 언어를 지원하지 않지만 가장 가까운 상위 언어를 지원하는 경우 기기 언어와 가장 가까운 언어가 사용됩니다. 예를 들어 사용자가 기기에서 언어로 스페인어(쿠바)를 선택했는데 앱이 스페인어(쿠바)를 지원하지 않고 상위 언어인 스페인어를 지원하는 경우 스페인어가 사용됩니다.

  5. 기기 언어가 앱에서 지원되지 않는 경우 영어가 사용됩니다.

외부 디플렉션 링크 아이콘 구성

앱의 저작물 카탈로그에 아이콘을 업로드하여 외부 디플렉션 링크 채널의 아이콘을 맞춤설정할 수 있습니다. 관리 포털의 설정 > 채팅 > 외부 디플렉션 링크 > 링크 보기 > 디플렉션 링크 추가에서 외부 디플렉션 링크를 만들 때 동일한 아이콘 이름을 사용해야 합니다. 관리 포털의 아이콘 이름이 앱에 업로드된 아이콘과 일치하지 않으면 SDK는 기본 아이콘을 사용합니다. 저작물 카탈로그에 이미지를 추가하는 방법은 이 링크를 참고하세요.

CRM으로 맞춤 데이터 전송

CRM 티켓에 맞춤 데이터를 전송할 수 있습니다.

커스텀 데이터를 전송하는 방법에는 두 가지가 있습니다.

  1. 보안 방법: JWT를 사용한 사전 정의된 데이터 서명

  2. 안전하지 않은 방법: 일반 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: 메서드를 호출하여 각각 NSString 또는 NSDictionary로 JSON 데이터를 설정하여 SDK로 채팅 스크립트를 전송할 수 있습니다.

UJETCustomData *customData = [UJETCustomData new];
[customData setExternalChatTransfer:jsonString];

UJETStartOptions *options = [UJETStartOptions new];
options.unsignedCustomData = customData;

[UJET startWithOptions:options];

JSON 형식:

  • greeting_override: 문자열

  • agent: 사전

    • name: 문자열

    • avatar: 문자열 [에이전트 아바타의 URL, 선택사항]

  • transcript: 배열

    • sender: 문자열[end_user 또는 agent]

    • timestamp: 문자열(예: 2021-03-15 12:00:00Z)

    • content: 배열

      • type: 문자열[text, media 중 하나]

      • text: 문자열[텍스트 유형에 필요]

      • media: 사전[미디어 유형에 필요]

        • type: 문자열[이미지, 동영상 중 하나]

        • url: 문자열 [미디어 파일을 가리키는 공개 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--)이 포함됩니다.

커스텀 데이터의 예

이 섹션에서는 커스텀 데이터의 예를 보여줍니다.

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 예시

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 속성을 포함하면 다음과 같은 동작이 발생합니다.

자세한 내용은 에이전트 어댑터에서 세션 데이터 보기를 참조하세요.

예약된 데이터 속성

세션이 시작될 때 예약된 데이터 속성을 서명된 커스텀 데이터로 Contact Center AI Platform (CCAI Platform)에 전송할 수 있습니다. 자세한 내용은 예약된 데이터 속성 전송을 참고하세요.

다음은 커스텀 데이터의 예약된 데이터 속성 예시입니다.

  {
    "custom_data": {
      "reserved_verified_customer": {
        "label": "Verified Customer",
        "value": false,
        "type": "boolean"
      },
      "reserved_bad_actor": {
        "label": "Bad Actor",
        "value": false,
        "type": "boolean"
      },
      "reserved_repeat_customer": {
        "label": "Repeat Customer",
        "value": false,
        "type": "boolean"
      }
    }
  }
  

각 값을 true 또는 false로 설정합니다.

  • reserved_verified_customer: 이 최종 사용자가 합법적인 고객이라고 생각하는 경우 true
  • reserved_bad_actor: 이 최종 사용자가 악의적인 행위자일 가능성이 있다고 생각되면 true입니다.
  • reserved_repeat_customer: 이 최종 사용자가 이전에 고객센터에 문의한 적이 있다고 판단한 경우 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 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 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) 통화에만 해당

    • 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) 하위 도메인

  • 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 = UJETGlobalOptions()
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)

방송 확장 프로그램

이 기능을 사용하려면 방송 확장 프로그램을 추가해야 합니다.

  1. Xcode 프로젝트를 엽니다.

  2. File> Target으로 이동합니다.

  3. Broadcast Upload Extension을 선택합니다.

  4. 타겟의 이름을 입력합니다.

  5. UI 확장 프로그램 포함 체크박스를 선택 해제합니다.

  6. 번들 ID를 기록하여 타겟을 만듭니다.

  7. 브로드캐스트 확장 프로그램의 타겟 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 키체인을 통해 일부 보안 비밀을 공유해야 합니다. 자체 키체인 그룹을 사용하여 이 작업을 수행하므로 나머지 앱 키체인과 격리됩니다.

Xcode에서 앱 타겟과 확장 프로그램 타겟을 모두 선택합니다. io.cobrowse 키체인 그룹에 키체인 공유 사용 권한을 추가합니다.

plist에 번들 ID 추가

이전에 만든 확장 프로그램의 번들 ID를 가져와 이 항목을 앱 Info.plist에 추가합니다.

xml
<key>CBIOBroadcastExtension</key>
<string>your.app.extension.bundle.ID.here</string>

확장 프로그램 구현

Xcode는 이전에 만든 타겟의 일부로 SampleHandler.m 및 SampleHandler.h (또는 SampleHandler.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
}

대체

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
    }
}

문제 해결

이 섹션에서는 일반적인 문제를 해결하는 데 도움이 되는 정보를 제공합니다.

앱 제출 거부

중국 지역에서 CallKit 프레임워크가 포함되어 제출된 앱이 거부됨

이러한 이유로 Apple에서 앱을 거부하는 경우 시스템이 VoIP 통화 시 중국 지역의 CallKit 프레임워크를 비활성화하도록 설계되었으므로 의견을 남기세요. 이는 SDK 버전 0.31.1부터 적용됩니다.

SDK 크기가 너무 큼

SDK 크기가 너무 커서 GitHub에서 추적하기 어려운 경우

이 도움말에서는 두 가지 옵션을 제공합니다. Git lfs를 사용하는 것이 좋습니다.

비트코드를 사용하지 않는 경우 바이너리에서 비트코드를 제거하는 것도 또 다른 옵션입니다. UJETKit.xcframework 폴더에서 이 명령어를 실행합니다.

xcrun bitcode_strip -r UJET -o UJET

dyld: 라이브러리가 로드되지 않음 오류

대상 > 빌드 설정 > 연결에서 Runpath 검색 경로에 @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)
    }
}