מדריך ל-iOS SDK

‫Contact Center AI Platform (פלטפורמת CCAI) Mobile SDK למערכת ההפעלה Apple iOS מאפשרת להטמיע את חוויית השימוש ב-CCAI Platform בנייד באפליקציות לנייד של iOS.

דרישות

יש את הדרישות הבאות ל-SDK לנייד ל-iOS:

  • ‫iOS גרסה 12.0 ואילך

אחזור פרטי הכניסה של החברה

  1. נכנסים לפורטל Contact Center AI Platform (CCAI Platform) באמצעות פרטי אדמין.

  2. עוברים אל הגדרות > הגדרות למפתחים.

  3. בקטעים Company Key (מפתח החברה) ו-Secret Code (קוד סודי), רושמים את מפתח החברה ואת הקוד הסודי של החברה.

תחילת העבודה

בהמשך מופיע מדריך לתחילת העבודה עם CCAI Platform iOS mobile SDK.

התקנה

כדי להתחיל, צריך להתקין את iOS SDK.

הורדת האפליקציה לדוגמה

  1. מורידים את אפליקציית הדוגמה ל-iOS.

  2. עוברים לתיקייה ומתקינים את קשרי התלות באמצעות CocoaPods:

    $ pod install --project-directory=ExampleApp
    
  3. כדי להגדיר במהירות את הגדרות הפרויקט, מריצים סקריפט של מעטפת:

    $ ./setup.sh
    

    אפשר גם לערוך את הגדרות הפרויקט באופן ידני. כדי לעשות זאת, מבצעים את השלבים הבאים:

    1. פותחים את ExampleApp.xcworkspace.

    2. מחליפים את הערכים UJETCompanyKey ו-UJETCompanySecret ב-Info.plist בערכים של מפתח החברה וקוד הסוד של החברה בדף הגדרות > הגדרות למפתחים בפורטל של פלטפורמת CCAI.

    3. מחליפים את הערך UJETSubdomain ב-Info.plist בתת-הדומיין בכתובת ה-URL של פורטל CCAI Platform. תת-הדומיין מופיע ישירות לפני .ujet.com בכתובת ה-URL – לדוגמה, your-subdomain ב-https://your-subdomain.ujet.com/settings/developer-setting.

שילוב בפרויקט

השילוב של iOS SDK באפליקציה תלוי בסביבת הפיתוח שלכם.

Swift package manager

  1. מוסיפים את חבילת Swift ל-iOS SDK.

  2. בהגדרות ה-Build, מזינים ‎-ObjC ב-Other Linker Flags.

  3. בגרסה האחרונה של Xcode (נכון לעכשיו, 13.2), יש בעיה מוכרת בשימוש ב-frameworks בינאריים שמופצים באמצעות Swift Package Manager. הפתרון הזמני הנוכחי לבעיה הזו הוא להוסיף שלב של הרצת סקריפט לשלבי הבנייה של פרויקט Xcode. השלב הזה של הפעלת סקריפט צריך להיות אחרי השלב של הטמעת מסגרות. השלב החדש הזה של הפעלת סקריפט צריך להכיל את הקוד הבא:

    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 במקום זאת.

קרתגו

Google Cloud מומלץ להשתמש במנהל תלות או בשילוב ידני כי תלות ב-CCAI Platform לא תומכת ב-Carthage.

כדי להשתמש במנהל תלות אחר, פועלים לפי השלבים הבאים:

  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 ליעד על ידי גרירתו לקטע Frameworks, Libraries, and Embedded Content.

  3. חוזרים על שלבים 2 ו-3 לכל התלות משלב 1.

  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

מפעילים את פלטפורמת CCAI באמצעות UJET_COMPANY_KEY ו-UJET_SUBDOMAIN.

In application:didFinishLaunchingWithOptions: method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // Initialize CCAI Platform
    [UJET.initialize:UJET_COMPANY_KEY subdomain:UJET_SUBDOMAIN delegate:self];

    // YOUR CODE

    return YES;
}

אפשר לשנות את רמת היומן מ-verbose ל-error. רמת ברירת המחדל של היומן היא UjetLogLevelInfo.

[UJET.setLogLevel:UjetLogLevelVerbose];

אימות משתמשי קצה

גישה ל-iOS SDK דרך אפליקציית iOS.

כדי לוודא שמשתמש הקצה מאומת, אנחנו מציגים את מנגנון החתימה של JWT.

ערכת ה-SDK ל-iOS תבקש לחתום על מטען הייעודי (payload) כשיידרש אימות. אם החתימה תתבצע בהצלחה, האפליקציה תחליף את ה-JWT החתום באסימון האימות של משתמש הקצה. צריך לקרוא לבלוק של הצלחה או כישלון לפני שהנציג מחזיר את הערך.

למשתמש אנונימי (המזהה הוא nil), האפליקציה תיצור UUID עבור המשתמש. אם בשלב מאוחר יותר המשתמש מאומת באמצעות מזהה, האפליקציה תנסה למזג את שני המשתמשים על סמך ה-UUID.

ב-UJETObject.h מתוך פרויקט לדוגמה:

@import UJETKit;

@interface UJETObject : NSObject <UJETDelegate>

הטמעה של signPayload: payloadType: הצלחה: כישלון: שיטת העברה.

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

מומלץ מאוד לחתום על מטען הייעודי (payload) משרת האפליקציות, ולא מהלקוח.

בדוגמה הזו נעשה שימוש בחתימה מקומית למטרות בדיקה. פרטים נוספים זמינים בקובץ UJETObject.m signDataInRemote: success: failure:.

מידע נוסף זמין במאמר אימות משתמשי קצה ב-SDK.

הגדרת התראות

האפליקציה שולחת התראות פוש כדי לבקש פעולות חכמות כמו אימות ותמונה, וגם כדי לדווח על שיחה נכנסת. כדי לשמור את האפליקציה בפורטל האדמינים, צריך לשמור שני סוגים שונים של אישורים (VoIP ו-APNs).

הכנת אישור לשירותי VoIP

מאמרי עזרה זמינים בנושא התראות פוש ל-VoIP של Apple.

  1. יוצרים ומורידים את אישור ה-VoIP מאתר המפתחים של Apple.

  2. לוחצים פעמיים על האישור כדי להוסיף אותו ל-Keychain.

  3. מפעילים את אפליקציית Keychain Access ב-Mac.

  4. בסרגל הצדדי הימני, בוחרים בקטגוריה My Certificates (האישורים שלי).

  5. לוחצים לחיצה ימנית על VoIP Services: your.app.id certificate (שירותי VoIP: אישור your.app.id).

  6. בתפריט הקופץ, בוחרים באפשרות ייצוא.

  7. שומרים אותו כ-cert.p12 בלי להגן עליו באמצעות סיסמה, כלומר משאירים את השדה password ריק.

  8. מריצים את הפקודה הבאה במסוף.

    openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -debug -showcert
    
  9. החלק העליון של cert.pem הוא האישור והחלק התחתון הוא המפתח הפרטי.

  10. בודקים שהאישור פועל עם שרת ההתראות בדחיפה של 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)
    ---
    
  11. מתחברים לפורטל של פלטפורמת CCAI באמצעות פרטי כניסה של אדמין ועוברים אל הגדרות > הגדרות למפתחים > אפליקציה לנייד.

  12. ממלאים את האישור בקטע 'אישור שירותי VoIP' ושומרים. חשוב להקפיד על תגי גבול (-----BEGIN----- ו------END-----) גם לאישור וגם למפתח הפרטי.

  13. מסמנים את התיבה 'ארגז חול' אם מריצים אפליקציה עם פרופיל הקצאת הרשאות לפיתוח, כמו ניפוי באגים ב-Xcode. אם האפליקציה שלכם מאוחסנת בארכיון לצורך הפצה אד-הוק או הפצה דרך חנות אפליקציות, והיא משתמשת בפרופיל הקצאת הרשאות להפצה, צריך לבטל את הסימון של תיבת הסימון Sandbox.

הכנה של SSL ל-Apple Push Notification Service

התהליך הזה דומה לתהליך של אישורים לשירותי VOIP. במקרה הזה נעשה שימוש באישור SSL של שירות ההתראות של אפל (סביבת Sandbox וסביבת Production). במאמר העזרה בנושא שרת ההתראות המרוחק של 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"]) {
    [UJET receivedNotification:payload.dictionaryPayload completion:completion];
  } else {
    completion();
  }
}

// UIApplicationDelegate

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  [UJET updatePushToken: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];
    }
}

הפעלת התראות בדחיפה

  1. בוחרים את היעד ופותחים את הכרטיסייה יכולות.

  2. מפעילים את המתג של התראות.

בדיקת התראות פוש

בקטעים הבאים מוסבר איך לבדוק את ההתראות בדחיפה.

קטע ניפוי הבאגים של התראות

בפורטל האדמין, עוברים אל 'הגדרות' > 'הגדרות למפתחים'. בדף הזה, מוצאים את הקטע Push Notification Debug (ניפוי באגים של התראות בדחיפה):

מעתיקים את טוקן המכשיר ומדביקים אותו באזור הטקסט בצד שמאל, ואז בוחרים את האפליקציה הנכונה לנייד.

קבלת טוקן המכשיר

דוגמה למחרוזת של אסימון מכשיר:

7db0bc0044c8a203ed87cdab86a597a2c43bf16d82dae70e8d560e88253364b7

התראות פוש מוגדרות בדרך כלל בכיתה שתואמת לפרוטוקול UIApplicationDelegate או PKPushRegistryDelegate. בשלב מסוים, טוקן המכשיר יהיה זמין לכם. אפשר להדפיס אותו לפני שמעבירים אותו ל-SDK של iOS. כדי לקבל את טוקן המכשיר, משתמשים בקטע הקוד.

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 של האישור ואת טוקן המכשיר, לוחצים על הלחצן.

אם הודעת הפוש לבדיקה נמסרה בהצלחה, התוצאה תהיה ההודעה Push notification successfully configured.

אי אפשר להבטיח ב-100% שהתראות פוש יגיעו, כי זה תלוי בחיבור הרשת של המכשיר.

הגדרות אישיות של פרויקט

בקטעים הבאים מפורטים השינויים שצריך לבצע כדי להגדיר את הפרויקט.

יכולות

בהגדרות היעד, מפעילים את היכולות הבאות:

  • הודעות שנשלחות מהאפליקציה

  • מצבי רקע (צריך לסמן את הפריטים האלה)

  • אודיו ו-AirPlay

  • Voice over IP

Info.plist

כדי להגן על פרטיות המשתמשים, כל אפליקציית iOS שמקושרת ל-iOS 10.0 ואילך, ושניגשת למיקרופונים, לספריית התמונות ולמצלמה של המכשיר, חייבת להצהיר על הכוונה לעשות זאת. צריך לכלול את המפתחות הבאים עם ערך מחרוזת בקובץ 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 באמצעות פרטי כניסה של אדמין.

  2. עוברים אל הגדרות > תור.

  3. בוחרים תור כלשהו ממבנה התפריט.

  4. בוחרים באפשרות יצירת נקודת גישה ישירה.

  5. מזינים את המפתח בטופס הטקסט.

  6. לוחצים על Save.

ניקוי המטמון מהמחשב המקומי אם נתוני המשתמש עודכנו

אנחנו מעבירים למטמון את אסימון האימות ב-Keychain כדי לעשות בו שימוש חוזר ולשלוח בקשות פחות תכופות לחתימת מטען ייעודי (payload) מאפליקציית המארח. ה-SDK ישתמש באסימון עד שהוא יפוג או עד שהוא יבוטל באמצעות קריאה ל-clearUserData. אפליקציית המארח אחראית לבטל את המטמון הזה בכל פעם שנתונים שקשורים למשתמש השתנו או עודכנו, למשל כשמתרחש אירוע של יציאה מהחשבון.

[UJET clearUserData];

בדיקה אם קיים סשן לפני הפעלת Contact Center AI Platform

לפני שמתחילים סשן, כדאי לבדוק שאין סשן פעיל. זה חשוב במיוחד אם מזהה המשתמש השתנה.

[UJET getStatus];

אם יש סשן קיים, צריך להציג למשתמש הנחיה להמשיך את הסשן או לבטל את הפעולה:

if ([UJET getStatus] != UjetStatusNone) {
  // Display alert to cancel login or resume existing session
}

התאמה אישית

יש כמה אפשרויות לשינוי העיצוב של ה-SDK שמפורטות במאמר UJETGlobalTheme.h.

הגדרת העיצוב אחרי [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 למצב כהה. מומלץ להגדיר את המאפיין הזה אם האפליקציה תומכת במצב כהה. במאמרים הבאים של אפל מוסבר איך לבחור את צבע הגוון המתאים למצב כהה:

עיצוב הצ'אט

כדי להתאים אישית את מסך הצ'אט, אפשר להשתמש במחרוזת 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)
}

הגדרת טופס אינטרנט

כדי להגדיר את היכולת של טופס האינטרנט, מטמיעים את השיטה ujetWebFormDidReceive של פרוטוקול UJETDelegate. השיטה הזו מקבלת אירוע (מילון 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. מעבירים את נתוני הטופס ל-SDK כמילון FormDataEvent באמצעות completion closure.

  4. אם מתרחשת שגיאה במהלך יצירת ה-URI או החתימה, מפעילים את הקריאה החוזרת באמצעות callback.onError() עם Error.

המילון (FormDataEvent) שמועבר ל-SDK צריך להיות במבנה הבא:

 {
    "type": "form_data",
    "signature": "4868a7e1dcb5...",
    "data": {
       "smart_action_id":1,
       "external_form_id": "form_id",
       "uri":"foobar"
    }
  }

החתימה (HMAC-SHA:256) צריכה להיווצר באמצעות data, ולחתום עליה באמצעות מפתח הסוד המשותף. לפני יצירת החתימות, צריך לסדר את מפתחות האובייקט של הנתונים בסדר אלפביתי ולשלוח את אותו data ל-SDK.

העברה אחרי סיום הסשן

אפשר להוסיף התאמה אישית של סיוע וירטואלי אחרי סיום הסשן יחד עם התאמה אישית של הצ'אט. אפשר לעשות את זה באמצעות קובץ ה-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)
}

תפריט הפעולות ב-Chat

אפשר להוסיף התאמה אישית לתפריט הפעולות בצ'אט יחד עם התאמה אישית של הצ'אט. אפשר לעשות את זה באמצעות קובץ ה-JSON (ראו מאפיין form_card) או באמצעות המחלקה UJETChatActionMenuTheme.

func customizeChatTheme() throws {
    guard let file = Bundle.main.path(forResource: "chat-theme-custom", ofType: "json") else { return }
    let json = try String.init(contentsOfFile: file, encoding: .utf8)

    let chatTheme = UJETChatTheme.init(jsonString: json)

    let actionMenuTheme = UJETChatActionMenuTheme()

    let photoLibraryIcon = UJETChatUserInputIconTheme()
    photoLibraryIcon.visible = true
    photoLibraryIcon.image = UJETImageRef(assetName: "library_button_asset")

    let cameraIcon = UJETChatUserInputIconTheme()
    cameraIcon.visible = true
    cameraIcon.image = UJETImageRef(assetName: "camera_button_asset")

    let cobrowseIcon = UJETChatUserInputIconTheme()
    cobrowseIcon.visible = true
    cobrowseIcon.image = UJETImageRef(assetName: "cobrowse_button_asset")

    actionMenuTheme.libraryIcon = photoLibraryIcon
    actionMenuTheme.cameraIcon = cameraIcon
    actionMenuTheme.cobrowseIcon = cobrowseIcon

    chatTheme?.actionMenu = actionMenuTheme

    let globalTheme = UJETGlobalTheme()
    globalTheme.chatTheme = chatTheme

    UJET.setGlobalTheme(globalTheme)
}

הופעות אחרות

אתם יכולים להתאים אישית גם את הגודל של הגופן ואת צבע הרקע.

theme.supportTitleLabelFontSize = 30;
theme.supportDescriptionLabelFontSize = 20;
theme.supportPickerViewFontSize = 30;
theme.staticFontSizeInSupportPickerView = YES;

theme.backgroundColor = UIColor.darkGrayColor;
theme.backgroundColorForDarkMode = UIColor.lightGrayColor;

CallKit

ב-iOS 10.0 ומעלה, CallKit מופעל לכל השיחות.

באמצעות CallKit, שיחה נכנסת מוצגת באפליקציה עם מסך השיחה, והשיחה מוצגת בהיסטוריית השיחות בטלפון.

כדי להתחיל סשן תמיכה חדש ב-CCAI Platform מתוך היסטוריית השיחות, מוסיפים את הבלוק הבא ל-AppDelegate.m:

AppDelegate.m:
- (BOOL)application:(UIApplication *)app continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler {
    if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) {
        // Open app from Call history
        [UJET startWithOptions:nil];
    }

    return YES;
}

‫CallKit מאפשרת להציג סמל בגודל 40x40 במסך הנעילה כשמתקבלת שיחה בזמן שהמכשיר נעול. ממקמים תמונה בקובץ Xcassets בשם icon-call-kit.

הגדרת ה-SDK

אפשר להגדיר כמה אפשרויות לפני שמפעילים את ה-SDK.

פרטים נוספים זמינים במחלקה UJETGlobalOptions.

UJETGlobalOptions *options = [UJETGlobalOptions new];
options.fallbackPhoneNumber = @"+18001112222";
options.preferredLanguage = @"en";

[UJET setGlobalOptions:options];

הצגה או הסתרה של לחצן הורדת התמליל

אפשר להגדיר את ה-SDK כך שיוצג או יוסתר לחצן להורדת התמליל בתפריט האפשרויות של הצ'אט ובמסך שאחרי הצ'אט.

בדוגמת הקוד הבאה אפשר לראות איך מגדירים את הלחצן להורדת התמליל:

typedef NS_OPTIONS(NSUInteger, UJETChatDownloadTranscriptVisibilityOptions) {
    UJETChatDownloadTranscriptVisibilityOptionsShowAll = 0,
    UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu = 1 << 0,
    UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen = 1 << 1,
    UJETChatDownloadTranscriptVisibilityOptionsHideAll = UJETChatDownloadTranscriptVisibilityOptionsHideFromOptionsMenu | UJETChatDownloadTranscriptVisibilityOptionsHideFromPostChatScreen
};

@property (nonatomic, assign) UJETChatDownloadTranscriptVisibilityOptions transcriptVisibilityOptions;

מעבר אוטומטי ל-PSTN

אנחנו מספקים מעבר אוטומטי ל-PSTN בכמה מצבים:

  • הרשת הסלולרית במצב אופליין.

  • אי אפשר להגיע לקצה העורפי של האפליקציה.

  • ה-VoIP לא זמין

    • מצב הרשת לא טוב מספיק כדי להתחבר. פרטים נוספים זמינים במאמר בנושא המאפיין UJETGlobalOptions.pstnFallbackSensitivity.

    • החיבור נכשל בגלל הגדרת חומת אש או בעיה בספק.

מומלץ להגדיר את מספר ה-IVR של החברה ב-UJETGlobalOptions.fallbackPhoneNumber. הפורמט המומלץ הוא סימן + ואחריו קידומת המדינה ומספר הטלפון. לדוגמה: ‫+18001112222.

רגישות לגיבוי למערכת הטלפוניה העולמית (PSTN)

אפשר לשנות את רמת הרגישות של בדיקת מצב הרשת למעבר אוטומטי ל-PSTN.

‪@property (nonatomic, assign) float pstnFallbackSensitivity;

הערך צריך להיות בטווח 0.0 עד 1.0. אם הערך מוגדר כ-1, השיחה תתבצע תמיד דרך PSTN ולא דרך VoIP. הערך המקסימלי של זמן האחזור והערך המינימלי של רוחב הפס הם 10,000 אלפיות השנייה ו-10KB/s בהתאמה, אם הערך הוא 0. לדוגמה, ערך של 0.5 מציין חביון מינימלי של 5, 000 אלפיות השנייה ורוחב פס מינימלי של 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.

דילוג על סקר שביעות הרצון

אתם יכולים להוסיף כפתור שיאפשר למשתמש לדלג על סקר שביעות רצון הלקוחות. בדוגמת הקוד הבאה אפשר לראות איך מוסיפים את הלחצן:

let options = UJETGlobalOptions()
options.skipCsat = true

התאמה אישית של אינדיקטור הפעילות

אתם יכולים להוסיף ל-SDK אנימציה משלכם של טעינה (בתוך UIView) ולשנות את ברירת המחדל UIActivityIndicatorView. מטמיעים את השיטה ujet_activityIndicator מ-UJETDelegate ומחזירים את התצוגה המותאמת אישית.

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
}

אם כבר הגדרתם את המאפיין UIUserInterfaceStyle כ-Light בקובץ Info.plist של האפליקציה כדי להשבית לחלוטין את המצב הכהה, אתם יכולים להתעלם מהמאפיין הזה.

שפה מועדפת

ה-SDK של פלטפורמת CCAI ישתמש בסדר העדיפויות הבא כדי לקבוע את השפה המועדפת.

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

שימוש בשיטה לא מאובטחת לשליחת נתונים בהתאמה אישית

השיטה הזו לא מומלצת כי היא יוצרת פגיעות פוטנציאלית שיכולה לחשוף את האפליקציה להתקפת אדם בתווך. אם תבחרו להשתמש בשיטה הזו, לא נישא באחריות לחשיפה לאבטחה ולנזק פוטנציאלי שעלולים להתרחש. מומלץ להשתמש בשיטה המאובטחת שתיארנו קודם כדי לשלוח נתונים בהתאמה אישית באפליקציה. אפשר גם פשוט להפעיל את iOS SDK עם מופע UJETCustomData. במקרה הזה, הפונקציה signPayload delegate for UJETPayloadCustomData צריכה פשוט להפעיל את הפונקציה 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];

שימוש בנתונים מותאמים אישית לא חתומים כדי לשלוח תמליל של שיחה חיצונית

אפשר לשלוח את תמליל הצ'אט ל-SDK כשהוא מופעל עם נתונים מותאמים אישית לא חתומים, על ידי קריאה לשיטה setExternalChatTransfer: או לשיטה setExternalChatTransferWithDictionary: כדי להגדיר נתוני JSON עם NSString או NSDictionary, בהתאמה.

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: array

    • sender: מחרוזת [end_user או agent]

    • timestamp: מחרוזת [לדוגמה, 2021-03-15 12:00:00Z]

    • content: array

      • type: מחרוזת [אחת מהאפשרויות text, media]

      • text: מחרוזת [חובה לסוג טקסט]

      • media: מילון [חובה לסוג מדיה]

        • type: מחרוזת [אחת מהאפשרויות image, video]

        • 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"
        }
      ]
    }
  ]
}

אפשר להשתמש ב-Markdown בסוג הטקסט. התחביר הנתמך כולל נטוי, הדגשה, רשימת תבליטים, היפר-קישור וקו תחתון (--text--).

דוגמה לנתונים מותאמים אישית

קובץ JSON מקודד ל-JWT

קובץ ה-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
  • מספר

    • מספר שלם, מספר ממשי
  • תאריך

    • פורמט חותמת זמן של Unix ב-UTC עם 13 ספרות. (כולל אלפיות שנייה)
  • כתובת URL

    • הפורמט של כתובת URL מסוג HTTP
דוגמה ל-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) כנתונים מותאמים אישית חתומים כשמתחיל סשן. מידע נוסף זמין במאמר בנושא שליחת מאפייני נתונים שמורים.

הדוגמה הבאה מציגה מאפייני נתונים שמורים בנתונים מותאמים אישית:

  {
    "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 אם קבעתם שמשתמש הקצה הזה יצר קשר עם מוקד התמיכה שלכם בעבר.

התאמה אישית של Flow

ניתוק של 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, גם אם למשתמש הקצה יש את האפליקציה וגם אם לא.

עוברים אל הגדרות > ניהול פעולות > הפעלת שליחת SMS להורדת האפליקציה בפורטל של פלטפורמת ה-CCAI.

אפשר להגדיר כתובת URL של אפליקציה עם דף האינטרנט (למשל, https://your-company.com/support) אחרי שמגדירים קישור אוניברסלי או סכימת כתובות URL מותאמות אישית. אפשר לבחור באחת מהאפשרויות.

הטמעה של שיטת delegate לטיפול בקישור עומק

הקישור האוניברסלי וכתובת ה-URL המותאמת אישית נראים כך: https://your-company.com/support?call_id=x&nonce=y ו- your-company://support?call_id=x&nonce=y בהתאמה. בפורטל Admin, מזינים את אחת מכתובות הקישור בלי פרמטרים של שאילתה בשדה App URL (כתובת ה-URL של האפליקציה). לדוגמה, מזינים את הערך your-company://support אם משתמשים בסכימת כתובות URL בהתאמה אישית.

בשיטת ההעברה, חשוב לוודא שמתבצעת קריאה רק ל-[UJET start] כשנתיבי כתובות ה-URL והפרמטרים בקישור האוניברסלי או בכתובת ה-URL המותאמת אישית ספציפיים ל-CCAI Platform.

- (BOOL)application:(UIApplication *)app continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler {
  ...
  if ([NSUserActivityTypeBrowsingWeb isEqualToString:userActivity.activityType]) {
    NSURL *url = userActivity.webpageURL;

    NSArray *availableSchema = @[
                                  @"your-company",   // custom URL scheme
                                  @"https"           // universal link
                                  ];

    NSArray *availableHostAndPath = @[
                                      @"ujet",                  // custom URL scheme
                                      @"your-comany.com/ujet"   // universal link
                                      ];

    if (![availableSchema containsObject:url.scheme]) {
      return NO;
    }

    NSString *hostAndPath = [NSString stringWithFormat:@"%@%@", url.host, url.path];
    if (![availableHostAndPath containsObject:hostAndPath]) {
      return NO;
    }

    // your-company://ujet?call_id={call_id}&nonce={nonce}
    // https://your-company.com/ujet?call_id={call_id}&nonce={nonce}
    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url
                                                resolvingAgainstBaseURL:NO];
    NSArray *queryItems = urlComponents.queryItems;
    NSString *callId = [self valueForKey:@"call_id" fromQueryItems:queryItems];
    // validate call id
    if (![self isValidCallId:callId]) {
      return NO;
    }
    NSString *nonce = [self valueForKey:@"nonce" fromQueryItems:queryItems];

    UJETStartOptions *options = [[UJETStartOptions alloc] initWithCallId:callId nonce:nonce];

    [UJET startWithOptions:options];
  }
  ...
}

אם האפליקציה שלכם משתמשת ב-UIWindowSceneDelegate, צריך להוסיף את קטע הקוד הזה:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        //if app is called with universal Link and started from cold
        if connectionOptions.urlContexts.first != nil  {
            self.scene(scene, openURLContexts: connectionOptions.urlContexts)
        }

        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func scene(_ scene: UIScene, willContinueUserActivityWithType userActivityType: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let _ = appDelegate.application(UIApplication.shared,
                                        continue: NSUserActivity(activityType: userActivityType)) { _ in

        }
    }

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url else {
            return
        }

        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let _ = appDelegate.application(UIApplication.shared,
                                        open: url,
                                        options: [:])
    }
}

מידע נוסף זמין בקובץ UJETObject+DeepLink עם דוגמאות לקוד.

התבוננות באירוע של פלטפורמת CCAI

אנחנו מפרסמים את האירועים הבאים דרך NSNotificationCenter.defaultCenter. אתם יכולים להאזין להם ולהתאים אישית את התהליך בהתאם לתרחיש השימוש, למשל, פריסת מקלדת מותאמת אישית.

  • UJETEventEmailDidClick

    • נתונים של תפריט התור
  • UJETEventEmailDidSubmit

    • נתונים של תפריט התור

    • has_attachment: (NSNumber) @YES, @NO

  • UJETEventSessionViewDidAppear

    • type: @"call", @"chat"

    • חותמת זמן: (NSString) ISO 8601

  • UJETEventSessionViewDidDisappear

    • type: @"call", @"chat"

    • חותמת זמן: (NSString) ISO 8601

  • UJETEventSessionDidCreate

    • נתוני סשן
  • UJETEventSessionDidEnd

    • נתוני סשן

    • agent_name: (NSString) null if agent didn't join

    • משך: (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) subdomain

  • device_model: (NSString)

  • device_version: (NSString)

  • sdk_version: (NSString)

  • חותמת זמן: (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: מוסיפים את ה-subspec הבא ליעד האפליקציה.

    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 צריכים להיות שני לחצנים, אחד לאישור ואחד לדחייה.

אחרי קבלת ההסכמה, מעבירים אותה ל-SDK שלנו על ידי קריאה לסגירה consentStatus. ‫UIViewController של cobrowseFullDeviceRequestAlert. האובייקט delegate צריך להכיל את השדה 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
    }
}

ל-view controller המותאם אישית צריך להיות סגירה להעברת סטטוס ההסכמה ל-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)
        }
    }
}

ל-view controller המותאם אישית של ההתראה על בקשה מלאה מהמכשיר צריכים להיות 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?()
        }
    }
}

אל תשכחו להעביר את הספק הזה ל-SDK שלנו דרך ה-API הבא:

let provider = CobrowseAlertProvider()
UJET.setCobrowseAlertProvider(provider)

תוסף שידור

כדי להשתמש בתכונה הזו, צריך להוסיף תוסף לשידור.

  1. פותחים את פרויקט Xcode.

  2. עוברים אל קובץ > יעד.

  3. בוחרים באפשרות Broadcast Upload Extension (תוסף להעלאת שידורים).

  4. מזינים שם ליעד.

  5. מבטלים את הסימון של האפשרות Include UI Extension (הכללת תוסף לממשק המשתמש).

  6. יוצרים את היעד ורושמים את מזהה החבילה שלו.

  7. משנים את גרסת ה-SDK של תוסף השידור ל-iOS 12.0 ומעלה.

שילוב ה-SDK

CocoaPods: מוסיפים את ה-subspec הבא ליעד התוסף:

target 'MyApp' do
  pod 'UJET'
  pod 'UJET/Cobrowse'
end
target 'MyAppExtension' do
  pod 'UJET/CobrowseExtension'
end

אם אתם משתמשים ב-SwiftPM, בוחרים במוצר UJETCobrowseExtension ומוסיפים אותו ליעד התוסף.

הגדרת שיתוף של מחזיק המפתחות

האפליקציה ותוסף האפליקציה שיצרתם קודם צריכים לשתף כמה סודות דרך ה-Keychain של iOS. הם עושים את זה באמצעות קבוצת Keychain משלהם, כך שהם מבודדים משאר ה-Keychain של האפליקציות שלכם.

גם ביעד האפליקציה וגם ביעד התוסף מוסיפים הרשאה לשיתוף מחזיק מפתחות עבור קבוצת מחזיקי המפתחות io.cobrowse.

הוספת מזהה החבילה לקובץ plist

לוקחים את מזהה החבילה של התוסף שיצרתם קודם ומוסיפים את הרשומה הבאה לקובץ Info.plist של האפליקציות (הערה: לא בקובץ Info.plist של התוספים), ומחליפים את מזהה החבילה הבא במזהה שלכם:

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

מזעור ה-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, ה-SDK של iOS מטפל בשגיאה.

בטבלה הבאה מוצגות השגיאות שהפונקציה 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 בשטח סין.

אם אפל דוחה את האפליקציה שלכם מהסיבה הזו, פשוט משאירים תגובה כי המערכת מתוכננת להשבית את מסגרת CallKit באזור סין בשיחות VoIP. השינוי הזה תקף החל מגרסת SDK‏ 0.31.1.

ה-SDK גדול מדי

כשהגודל של ה-SDK גדול מדי וקשה לעקוב אחריו ב-GitHub

במאמר הזה מוצגות שתי אפשרויות. מומלץ להשתמש ב-Git lfs.

אם אתם לא משתמשים ב-Bitcode, אפשרות נוספת היא להסיר את ה-Bitcode מהקובץ הבינארי. מריצים את הפקודה הזו בתיקייה UJETKit.xcframework.

xcrun bitcode_strip -r UJET -o UJET

‫dyld: שגיאה בספרייה שלא נטענה

מוסיפים @executable_path/Frameworks אל Runpath Search Paths (נתיבי חיפוש של Runpath) מתוך Target > Build Settings (הגדרות Build) > Linking (קישור).

שליחת אפליקציה ב-iTunes Connect

יכול להיות שאפל תשאל את השאלה הבאה במהלך תהליך הבדיקה בגלל מצב הרקע של Voice over IP שהופעל:

האם המשתמשים יכולים לקבל שיחות VoIP באפליקציה שלכם?

עונים על השאלה בתשובה כן.

התראות על התראות לא זמינות כשמפעילים את ה-SDK

כדאי לבדוק את הדברים הבאים:

  • צריך להשתמש במכשיר אמיתי ולא בסימולטור.

  • מפעילים את ההתראות בדחיפה ואת מצבי הרקע > יכולת Voice over IP ‎.

אם זה לא עוזר, אפשר לנסות ליצור פרופיל הקצאת הרשאות להפצה (Ad-hoc או Apple Store).

בדיקת התראות פוש באפליקציית הבדיקה

מכינים את האישור של ה-VoIP ואת טוקן המכשיר.

בפורטל של CCAI Platform, מעיינים בקטע Push Notification Debug בתפריט Settings > Developer Settings.

אם כבר הגדרתם את האישור ל-APNS, אתם לא צריכים להזין את האישור שוב.

מזינים את האישור (אופציונלי) ומסמנים אם מדובר בארגז חול או לא (אופציונלי), ומזינים את טוקן המכשיר של הודעת הפוש של אפליקציית הבדיקה.

התחלת צ'אט חדש נמשכה יותר מ-30 שניות

בודקים אם אתם מגיבים לשיטה של נתונים מותאמים אישית שהוקצו לנציג. צריך להחזיר נתונים מותאמים אישית תקינים לפי בקשה, או להחזיר nil בבלוק ההצלחה.

אפשר להשתמש בקטע הקוד הבא כדוגמה להגדרה:

public func signPayload(_ payload: [AnyHashable: Any]?, payloadType: UjetPayloadType, success: (String?) -> Void, failure: (Error?) -> Void)
{
    if payloadType == .customData {
        success(nil)
    }
}