ערכת ה-SDK לנייד של Contact Center AI Platform (פלטפורמת CCAI) למערכת ההפעלה Apple iOS מאפשרת להטמיע את חוויית השימוש בנייד של פלטפורמת CCAI באפליקציות לנייד של iOS.
דרישות
אלה הדרישות של ה-SDK לנייד ל-iOS:
- iOS 12.0 ומעלה
אחזור פרטי הכניסה של החברה
נכנסים לפורטל של Contact Center AI Platform (CCAI Platform) באמצעות פרטי כניסה של אדמין.
עוברים אל הגדרות > הגדרות למפתחים.
בקטעים Company Key ו-Secret Code, רושמים בצד את מפתח החברה ואת הקוד הסודי של החברה.
תחילת העבודה
במדריך הבא מוסבר איך להתחיל להשתמש ב-SDK לנייד של CCAI Platform ל-iOS.
התקנה
כדי להתחיל, צריך להתקין את ה-SDK ל-iOS.
הורדת האפליקציה לדוגמה
מורידים את האפליקציה לדוגמה ל-iOS.
עוברים לתיקייה ומתקינים את יחסי התלות באמצעות CocoaPods:
$ pod install --project-directory=ExampleAppכדי להגדיר במהירות את הגדרות הפרויקט, מריצים סקריפט מעטפת:
$ ./setup.shלחלופין, אפשר לערוך ידנית את הגדרות הפרויקט:
פותחים את
ExampleApp.xcworkspace.מחליפים את הערכים
UJETCompanyKeyו-UJETCompanySecretב-Info.plistבערכים Company Key ו-Company Secret Code מהדף Settings > Developer Settings בפורטל של פלטפורמת CCAI.מחליפים את הערך
UJETSubdomainב-Info.plistבתת-הדומיין בכתובת ה-URL של הפורטל שלכם ב-CCAI Platform. תת-הדומיין מופיע ישירות לפני.ujet.comבכתובת ה-URL – לדוגמה,your-subdomainבכתובתhttps://your-subdomain.ujet.com/settings/developer-setting.
שילוב בפרויקט
השילוב של iOS SDK באפליקציה תלוי בסביבת הפיתוח שלכם.
Swift Package Manager
מוסיפים את Swift Package for iOS SDK מ-GitHub.
בהגדרות ה-build, מוסיפים את -ObjC ל-Other Linker Flags.
ב-Xcode 13.2 יש בעיה ידועה בשימוש במסגרות בינאריות שמופצות באמצעות Swift Package Manager. הפתרון הזמני לבעיה הזו הוא להוסיף Run Script Phase ל-Build Phases של פרויקט Xcode. השלב הזה של הרצת הסקריפט צריך להופיע אחרי שלב ה-build של Embed Frameworks. השלב החדש של הרצת הסקריפט צריך להכיל את הקוד הבא:
find "${CODESIGNING_FOLDER_PATH}" -name '*.framework' -print0 | while read -d $'0' framework do codesign --force --deep --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements --timestamp=none "${framework}" done
CocoaPods
מוסיפים את השורה הבאה ל-Podfile:
pod 'UJET', :podspec => 'https://sdk.ujet.co/ios/x.y.z/ujet.podspec' #specific version x.y.zמריצים את pod install. אם כבר שילבתם את iOS SDK, הריצו במקום זאת את הפקודה pod update CCAI Platform.
קרתגו
Google Cloud ממליצה להשתמש במנהל יחסי תלות או בשילוב ידני, כי יחסי התלות של פלטפורמת CCAI לא תומכים ב-Carthage.
כדי להשתמש במנהל תלויות אחר, פועלים לפי השלבים הבאים:
מוסיפים את השורות הבאות:
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"מריצים את
carthage bootstrap --use-xcframeworks(או אתcarthage update --use-xcframeworksאם מעדכנים יחסי תלות).
שילוב ידני
מורידים את
UJETKit.xcframework,UJETFoundationKit.xcframework,UJETChatRedKit.xcframework,UJETChatBlueKit.xcframework,UJETTwilioCallKit.xcframeworkואת כל יחסי התלותTwilioVoice.xcframeworkו-TwilioConversationsClient.xcframework.מוסיפים את UJETKit.xcframework ליעד על ידי גרירה שלו לקטע Frameworks, Libraries, and Embedded Content.
חוזרים על שלבים 2 ו-3 בכל יחסי התלות משלב 1.
ב-Build Settings (הגדרות ה-build), מגדירים את
-ObjCב-Other Linker Flags.מוסיפים את
libc++.tbdכתלות בקטעLinked Frameworksשל היעד.
אם אתם מתכוונים ליצור את ה-SDK באופן ידני באמצעות פרויקט לדוגמה, בצעו את השלבים בקטע הבא.
איך יוצרים את ה-SDK באופן ידני באמצעות פרויקט לדוגמה
כך עושים את זה:
מורידים את כל ה-frameworks, כולל
UJETKit.xcframeworkויחסי תלות אחרים.יוצרים את התיקייה CCAI Platform בתיקיית הבסיס של הפרויקט ומחלצים את כל ה-frameworks.
בוחרים יעד
Objc-ManualאוSwift-Manualויוצרים את ה-build.
מסגרת הייבוא
בקטעים הבאים מוסבר איך לייבא את ה-framework.
פרויקט Objective-C
@import UJETKit;
פרויקט ב-Swift
swiftimport
UJETimport UJETKit
אתחול ה-SDK
מפעילים את CCAI Platform באמצעות 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: 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;
}
מומלץ מאוד לחתום על המטען הייעודי (payload) משרת האפליקציה, ולא בלקוח.
בדוגמה הזו משתמשים בחתימה מקומית למטרות בדיקה. אפשר לעיין ב-signDataInRemote:
success: failure: בקובץ UJETObject.m.
מידע נוסף זמין במאמר אימות משתמשי קצה ב-SDK.
הגדרת התראות
פלטפורמת CCAI שולחת התראות כדי לבקש פעולות חכמות כמו אימות ותמונה, וגם כדי לדווח על שיחה נכנסת. אם התורים של הצ'אט משתמשים ב-Check In (נדרש iOS SDK בגרסה 2.16.0 ואילך), פלטפורמת CCAI שולחת התראה כדי לבקש מהצרכנים שממתינים לחזור לצ'אט ולאשר שהם מוכנים. איך מדווחים על בעיות ב-Mobile SDK ב-CCAI Platform צריך לשמור שני סוגים שונים של אישורים (VoIP ו-APNs) בפורטל הניהול של CCAI Platform.
הגדרת אישורים
ב-CCAI Platform צריך לשמור שני סוגים שונים של אישורי Apple Push בפורטל הניהול של CCAI Platform:
אישור SSL של Apple Push Notification service (APNs) – משמש להתראות רגילות של התראות מרוחקות.
אישור לשירותי VoIP – משמש להתראות על שיחות נכנסות ולפעולות חכמות מסוימות שקשורות לשיחות.
שני האישורים נוצרים בפורטל המפתחים של אפל, מומרים לפורמט PEM ואז מועלים לפורטל הניהול של CCAI Platform.
יצירת אישור SSL ל-APNs
כדי ליצור אישור SSL ל-APNs, פועלים לפי השלבים הבאים שמבוססים על המדריך הרשמי של אפל, Establishing a Certificate-Based Connection to APNs.
נכנסים אל developer.apple.com ועוברים אל Certificates, identifiers & profiles.
לוחצים על הכפתור + כדי ליצור אישור חדש.
בקטע Services, בוחרים באפשרות Apple push notification service SSL (Sandbox & production).
בוחרים את מזהה האפליקציה שתואם למזהה החבילה של האפליקציה ולוחצים על Continue (המשך).
מעלים בקשה לחתימה על אישור (CSR) שנוצרה מגישה ל-Keychain ב-Mac:
פותחים את Keychain access > Certificate assistant > Request a certificate from a certificate authority.
מזינים את כתובת האימייל, משאירים את השדה CA email address ריק, בוחרים באפשרות Saved to disk ולוחצים על Continue.
לוחצים על המשך ואז על הורדת קובץ
.cerשנוצר.לוחצים לחיצה כפולה על הקובץ
.cerכדי להתקין אותו ב-Keychain.ב-Keychain access, מאתרים את האישור שהותקן, לוחצים עליו לחיצה ימנית ובוחרים באפשרות Export… כדי לשמור אותו כקובץ
.p12. תתבקשו להגדיר סיסמה לייצוא.ממירים את הקובץ
.p12לפורמט PEM באמצעותopenssl:
# 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 מאפשרים לאפליקציה לקבל התראות בעדיפות גבוהה דרך מסגרת PushKit של אפל, שיכולה להעיר את האפליקציה ממצב סגור כדי לטפל בשיחות נכנסות. כדי לראות את השלבים הרשמיים העדכניים, עיינו במאמר יצירת אישורים לשירותי VoIP במאמרי העזרה של Apple למפתחים.
נכנסים אל developer.apple.com ועוברים אל Certificates, Identifiers & Profiles (אישורים, מזהים ופרופילים).
לוחצים על הכפתור + כדי ליצור אישור חדש.
בקטע Services (שירותים), בוחרים באפשרות VoIP services certificate (אישור לשירותי VoIP).
בוחרים את מזהה האפליקציה שתואם למזהה החבילה של האפליקציה ולוחצים על Continue.
מעלים את אותה בקשה לחתימה על אישור (CSR) שיצרתם בקטע הקודם (או יוצרים בקשה חדשה).
לוחצים על המשך ואז על הורדת קובץ ה-
.cerשנוצר.לוחצים לחיצה כפולה על הקובץ
.cerכדי להתקין אותו ב-Keychain.ב-Keychain access, מאתרים את האישור המותקן של שירותי VoIP, לוחצים עליו לחיצה ימנית ובוחרים באפשרות Export… כדי לשמור אותו כקובץ
.p12.ממירים את הקובץ
.p12לפורמט PEM באמצעותopenssl:
# 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
נכנסים לפורטל Admin של פלטפורמת CCAI.
עוברים אל הגדרות > הגדרות למפתחים > אפליקציה לנייד.
מעלים את אישור ה-PEM של APNs.
מעלים את אישור ה-PEM של ה-VoIP.
הפעלת התראות
בוחרים את היעד ופותחים את הכרטיסייה Capabilities (יכולות).
מפעילים את המתג של התראות.
התראות בדיקה
בקטעים הבאים מוסבר איך לבדוק את ההתראות.
קטע ניפוי באגים בהתראות
בפורטל האדמין, עוברים אל Settings > Developer Settings. בדף הזה, מוצאים את הקטע ניפוי באגים של התראות:

מעתיקים את אסימון המכשיר ומדביקים אותו באזור הטקסט המתאים, ובוחרים את האפליקציה המתאימה לנייד.
איך מקבלים את הטוקן של המכשיר
דוגמה למחרוזת של טוקן מכשיר:
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 של האישור ואת הטוקן של המכשיר, לוחצים על הכפתור.
אם התוצאה היא Push notification successfully configured, סימן שההתראה נשלחה בהצלחה.
לא מובטח שהתראת Push תתקבל, כי זה תלוי בחיבור של המכשיר לרשת.
הגדרות אישיות של פרויקט
בקטעים הבאים מפורטים השינויים שצריך לבצע כדי להגדיר את הפרויקט.
יכולות
בהגדרות היעד, מפעילים את היכולות הבאות:
הודעות שנשלחות מהאפליקציה
מצבי רקע (צריך לבדוק את הפריטים האלה)
אודיו ו-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];
אפשר גם להפעיל את ה-SDK ל-iOS מנקודה ספציפית בתפריט באמצעות המפתח הזה, דרך נקודת גישה ישירה:
UJETStartOptions *option = [[UJETStartOptions alloc] initWithMenuKey:@"MENU_KEY"];
[UJET startWithOptions:option];
כדי ליצור את menuKey, צריך ליצור נקודת גישה ישירה (DAP). כדי ליצור DAP:
נכנסים לפורטל של CCAI Platform באמצעות פרטי כניסה של אדמין.
עוברים אל הגדרות > תור.
בוחרים תור כלשהו ממבנה התפריט.
לוחצים על יצירת נקודת גישה ישירה.
מזינים את המפתח בטופס הטקסט.
לוחצים על 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)
}
הגדרת טופס באינטרנט
כדי להגדיר את היכולת של טופס האינטרנט, מטמיעים את ה-method ujetWebFormDidReceive של הפרוטוקול UJETDelegate. השיטה הזו מקבלת אירוע (מילון FormMessageReceivedEvent) כפרמטר, שמכיל מידע שקשור לטופס. מילון האירוע (FormMessageReceivedEvent) כולל את מבנה ה-JSON הבא:
{
"type": "form_message_received",
"smart_action_id": 1,
"external_form_id": "external_foobar"
"signature": "4868a7e1dcb5..."
}
כדי לטפל באירוע:
מחלצים את המידע הרלוונטי ממילון האירועים (
smart_action_id,external_form_idו-signature).יוצרים URI של טופס וחתימה לנתוני הטופס.
מעבירים את נתוני הטופס ל-SDK כמילון
FormDataEventבאמצעותcompletion closure.אם מתרחשת שגיאה במהלך יצירת ה-URI או החתימה, צריך להפעיל את הקריאה החוזרת (callback) באמצעות
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. הערך 0 מייצג את זמן האחזור המקסימלי (10,000 אלפיות השנייה) ואת סף רוחב הפס המינימלי (10KB/s). לדוגמה, אם הערך הוא 0.5, זמן האחזור המינימלי הוא 5,000 אלפיות השנייה ורוחב הפס המינימלי הוא 15KB/s.
כדי להגדיר את הערך הזה:
נכנסים לפורטל של CCAI Platform כאדמינים.
עוברים אל הגדרות > הגדרות למפתחים > אפליקציות לנייד.
מוצאים את הקטע סף למספר טלפון חלופי. ערך ברירת המחדל הוא 0.85.
מציינים את ערך הסף החדש.
לוחצים על שמירה.
השבתת ההתראות ברמה הגלובלית
אתם יכולים להשבית את ההתראות ברמה הגלובלית. הגדרת המאפיין הבא לערך false עוקפת את כל יחסי התלות של התראות, ומונעת מהתראות להגיע למשתמשי הקצה:
@property (nonatomic, assign) BOOL allowsPushNotifications;
התעלמות מהמצב הכהה
אפשר להתעלם מהמצב הכהה ב-CCAI Platform SDK באופן ספציפי באמצעות המאפיין הזה:
@property (nonatomic, assign) BOOL ignoreDarkMode;
הסתרת שורת הסטטוס
אפשר לשלוט בחשיפה של שורת הסטטוס באמצעות המאפיין הזה:
@property (nonatomic, assign) BOOL hideStatusBar;
כברירת מחדל, hideStatusBar מוגדר ל-false ול-visible.
דילוג על סקר שביעות הרצון
אתם יכולים להוסיף כפתור שמאפשר למשתמש לדלג על סקר שביעות הרצון. בדוגמת הקוד הבאה אפשר לראות איך מוסיפים את הכפתור:
let options = UJETGlobalOptions()
options.skipCsat = true
התאמה אישית של אינדיקטור הפעילות
אתם יכולים להוסיף אנימציית טעינה משלכם (בתוך UIView) ל-SDK ולבטל את ברירת המחדל של UIActivityIndicatorView. מטמיעים את ה-method 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 Platform תשתמש בסדר העדיפויות הבא כדי לקבוע את השפה המועדפת.
השפה שנבחרה במסך הפתיחה באפליקציה.
שפת ברירת המחדל נבחרה מתוך
UJETGlobalOptions. אפשר להגדיר את שפת ברירת המחדל באמצעות המאפייןpreferredLanguage. קודי השפות הנתמכות מופיעים בקובץUJETGlobalOptions.h.השפה שנבחרה במכשיר (בהגדרות > כללי > שפה ואזור) תהיה בשימוש, אם היא נתמכת באפליקציה.
אם האפליקציה לא תומכת בשפה שמוגדרת במכשיר, אבל תומכת בניב האב הכי קרוב, היא תשתמש בניב הכי קרוב לשפה שמוגדרת במכשיר. לדוגמה, אם המשתמש בחר בספרדית קובה כשפה במכשיר, והאפליקציה לא תומכת בספרדית קובה אבל תומכת בניב הראשי ספרדית, אז ייעשה שימוש בשפה הספרדית.
אם שפת המכשיר לא נתמכת באפליקציה, השפה תהיה אנגלית.
הגדרת סמלי קישורים להפניה החוצה
כדי להתאים אישית את הסמל בערוץ של קישור ההפניה החיצוני, מעלים סמל לקטלוג הנכסים של האפליקציה ומוודאים שמשתמשים באותו שם סמל כשיוצרים קישור הפניה חיצוני בפורטל האדמין, בקטע "הגדרות" > "צ'אט" > "קישורי הפניה חיצוניים" > "הצגת קישורים" > "הוספת קישור הפניה". אם שם הסמל בפורטל האדמין לא תואם לסמל שהועלה לאפליקציה, ערכת ה-SDK תשתמש בסמל ברירת המחדל. בקישור הזה מוסבר איך להוסיף תמונות לקטלוג הנכסים.



שליחת נתונים בהתאמה אישית למערכת לניהול קשרי לקוחות (CRM)
אתם יכולים לשלוח נתונים בהתאמה אישית לכרטיס ה-CRM.
יש שתי שיטות לשליחת נתונים בהתאמה אישית:
שיטה מאובטחת: חתימה על נתונים מוגדרים מראש באמצעות JWT.
שיטה לא מאובטחת: נתונים מוגדרים מראש עם JSON פשוט (לא מומלץ).
שימוש בשיטה המאובטחת לשליחת נתונים בהתאמה אישית
צריך להטמיע שיטת חתימה. קודם כל, אתם יכולים להוסיף את הנתונים בהתאמה אישית בצד הלקוח, ולשלוח אותם לשרת כדי לחתום עליהם. בשרת שלכם אתם יכולים להוסיף נתונים נוספים לפי טופס מוגדר, לחתום עליהם באמצעות company.secret ולהחזיר אותם באמצעות JWT.
- (void)signPayload:(NSDictionary *)payload payloadType:(UjetPayloadType)payloadType success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure
{
if (payloadType == UjetPayloadCustomData) {
// sign custom data using UJET_COMPANY_SECRET on your server.
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] init];
mutableRequest.URL = [NSURL URLWithString:@"https://your.company.com/api/ujet/sign/custom_data"];
mutableRequest.HTTPMethod = @"POST";
NSError *error;
// Make client's custom data
UJETCustomData *customData = [[UJETCustomData alloc] init];
[customData set:@"name" label:@"Name" stringValue:@"USER_NAME"];
[customData set:@"os_version" label:@"OS Version" stringValue:[[UIDevice currentDevice] systemVersion]];
[customData set:@"model" label:@"Model number" numberValue:[NSNumber numberWithInteger:1234]];
[customData set:@"temperature" label:@"Temperature" numberValue:[NSNumber numberWithFloat:70.5]];
[customData set:@"purchase_date" label:@"Purchase Date" dateValue:[NSDate date]];
[customData set:@"dashboard_url" label:@"Dashboard" urlValue:[NSURL URLWithString:@"http://internal.dashboard.com/1234"]];
NSDictionary *data = @{@"custom_data": [customData getData]};
mutableRequest.HTTPBody = [NSJSONSerialization dataWithJSONObject:data options:0 error:&error];
NSURLSessionDataTask *task = [session dataTaskWithRequest:mutableRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error) {
failure(error);
}
else {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
success(json[@"jwt"]);
}
}];
[task resume];
}
}
שימוש בשיטה לא מאובטחת לשליחת נתונים מותאמים אישית
השיטה הזו לא מומלצת כי היא יוצרת נקודת חולשה פוטנציאלית שעלולה לחשוף את האפליקציה להתקפת אדם בתווך. אם תבחרו להשתמש בשיטה הזו, לא נהיה אחראים לחשיפה של פרטים רגישים ולנזק שעלול להיגרם. מומלץ להשתמש בשיטה המאובטחת שתיארנו קודם כדי לשלוח נתונים בהתאמה אישית באפליקציה. אפשר גם פשוט להפעיל את ה-SDK ל-iOS עם מופע של UJETCustomData. במקרה כזה, הנציג של signPayload עבור 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: מערך
sender: מחרוזת [end_userאוagent]
timestamp: מחרוזת [לדוגמה,2021-03-15 12:00:00Z]content: מערך
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
מספר
- מספר שלם, מספר ממשי (float)
תאריך
- פורמט חותמת זמן של UTC Unix עם 13 ספרות. (מכיל אלפיות שנייה)
כתובת URL
- פורמט של כתובת URL מסוג HTTP
דוגמה ל-CRM
הנה דוגמה ל-CRM.
מיקום
משתמשים ב-CoreLocation framework. מידע נוסף זמין במאמר AppDelegate.m.
גרסת מערכת ההפעלה של המכשיר
[customData set:@"os_version" label:@"OS Version" stringValue:[[UIDevice currentDevice] systemVersion]];
מניעת הצגה של נתונים בהתאמה אישית
אפשר להשתמש במאפיין invisible_to_agent עם אובייקט נתונים בהתאמה אישית כדי למנוע הצגה של נתונים חתומים או לא חתומים בהתאמה אישית במתאם של הנציג. בדוגמה הקודמת, מספר תעודת הזהות של משתמש הקצה לא מוצג במתאם של הנציג כי "invisible_to_agent" : true נכלל באובייקט ssn.
כשכוללים את המאפיין "invisible_to_agent" : true באובייקט נתונים בהתאמה אישית,
ההתנהגות הצפויה היא:
- הנתונים המותאמים אישית נכללים בקובץ המטא-נתונים של הסשן.
- הנתונים בהתאמה אישית לא נכללים ברשומות של ה-CRM.
מידע נוסף זמין במאמר הצגת נתוני סשן במתאם של הנציג.
מאפייני נתונים שמורים
אתם יכולים לשלוח מאפייני נתונים שמורים אל Contact Center AI Platform (CCAI Platform) כנתונים חתומים בהתאמה אישית בתחילת הסשן. מידע נוסף זמין במאמר שליחת מאפייני נתונים שמורים.
דוגמה למאפייני נתונים שמורים בנתונים מותאמים אישית:
{
"custom_data": {
"reserved_verified_customer": {
"label": "Verified Customer",
"value": 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אם קבעתם שמשתמש הקצה הזה יצר קשר עם מוקד התמיכה שלכם בעבר.
התאמה אישית של 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, גם אם למשתמש הקצה יש את האפליקציה וגם אם לא.
בפורטל של פלטפורמת CCAI, עוברים אל הגדרות > ניהול פעולות > הפעלת שליחת SMS להורדת האפליקציה.
אפשר להגדיר את כתובת ה-URL של האפליקציה עם דף האינטרנט (למשל, https://your-company.com/support) אחרי שמגדירים קישור אוניברסלי או סכימת כתובות URL מותאמת אישית. אפשר לבחור בכל אחת מהדרכים.
הטמעת שיטת נציג לטיפול בקישור עומק
הקישור האוניברסלי וכתובת ה-URL המותאמת אישית נראים כך:
https://your-company.com/support?call_id=x&nonce=y ו-
your-company://support?call_id=x&nonce=y בהתאמה. מזינים את אחד מהקישורים בלי פרמטרים של שאילתה בשדה "כתובת URL של האפליקציה" בפורטל Admin. לדוגמה, אם משתמשים בסכמה של כתובת URL בהתאמה אישית, צריך להזין your-company://support.
בשיטת הנציג, חשוב לוודא שאתם קוראים ל-[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 Platform
אנחנו מפרסמים את האירועים הבאים דרך NSNotificationCenter.defaultCenter. אתם יכולים להאזין להם ולהתאים אישית את התהליך בהתאם לתרחיש לדוגמה, למשל פריסת מקלדת בהתאמה אישית.
UJETEventEmailDidClick
- נתוני התפריט של התור
UJETEventEmailDidSubmit
נתוני התפריט של התור
has_attachment: (NSNumber) @YES, @NO
UJETEventSessionViewDidAppear
סוג: @"call", @"chat"
timestamp: (NSString) ISO 8601
UJETEventSessionViewDidDisappear
סוג: @"call", @"chat"
timestamp: (NSString) ISO 8601
UJETEventSessionDidCreate
- נתוני סשנים
UJETEventSessionDidEnd
נתוני סשנים
agent_name: (NSString) ערך null אם הנציג לא הצטרף
duration: (NSNumber) only for call
ended_by: (NSString)
type=call: @"agent", @"end_user"
type=chat: @"agent", @"end_user", @"timeout", @"dismissed"
UJETEventSdkDidTerminate
UJETEventPostSessionOptInDidSelected
- opt_in_selected: (NSString) @"Yes", @"No"
נתוני האירוע
מטא-נתונים
application: @"iOS"
app_id: מזהה החבילה (NSString)app_version: (NSString)company: (NSString) subdomaindevice_model: (NSString)device_version: (NSString)sdk_version: (NSString)timestamp: (NSString) ISO 8601
נתוני התפריט של רשימת הבאים בתור
מטא-נתונים
menu_id: NSStringmenu_key: NSString, nullablemenu_name: NSStringmenu_path: NSString
נתוני סשן
מטא-נתונים
session_id: NSStringtype: @"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 באמצעות ה-method המתאימה של הפרוטוקול. UIViewController
צריכה לכלול שני לחצנים: אחד לאישור ואחד לדחייה.
אחרי שמקבלים את ההסכמה, מעבירים אותה ל-SDK שלנו באמצעות קריאה ל-closure
consentStatus. UIViewController של cobrowseFullDeviceRequestAlert. הנציג צריך להכיל את 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
}
}
ב-custom view controller צריך להיות closure כדי להעביר את סטטוס ההסכמה ל-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?()
}
}
}
חשוב להעביר את הספק הזה ל-SDK שלנו דרך ה-API הבא:
let provider = CobrowseAlertProvider()
UJET.setCobrowseAlertProvider(provider)
תוסף שידור
כדי להשתמש בתכונה הזו, צריך להוסיף תוסף שידור.
פותחים את הפרויקט ב-Xcode.
עוברים אל קובץ > יעד.
בוחרים באפשרות Broadcast Upload Extension (תוסף להעלאת שידור).
מזינים שם ליעד.
מבטלים את הסימון בתיבה Include UI Extension.
יוצרים את היעד ורושמים את מזהה החבילה שלו.
משנים את ערכת ה-SDK המטורגטת של Broadcast Extension ל-iOS 12.0 ואילך.
שילוב ה-SDK
CocoaPods{: מוסיפים את ה-subspec הבא ליעד של התוסף:
target 'MyApp' do
pod 'UJET'
pod 'UJET/Cobrowse'
end
target 'MyAppExtension' do
pod 'UJET/CobrowseExtension'
end
אם אתם משתמשים ב-SwiftPM, בחרו את המוצר UJETCobrowseExtension והוסיפו אותו ליעד של התוסף.
הגדרת שיתוף של צרור מפתחות
האפליקציה ותוסף האפליקציה שיצרתם קודם צריכים לשתף כמה סודות דרך מחזיק המפתחות של iOS. הם עושים זאת באמצעות קבוצת Keychain משלהם, כך שהם מבודדים משאר ה-Keychain של האפליקציות שלכם.
ב-Xcode, בוחרים גם את יעד האפליקציה וגם את יעד התוסף. מוסיפים הרשאת שיתוף של keychain לקבוצת ה-keychain של io.cobrowse.
מוסיפים את מזהה החבילה לקובץ ה-plist
מעתיקים את מזהה החבילה של התוסף שיצרתם קודם ומוסיפים את הרשומה הזו לקובץ 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
אפשר למזער את ה-SDK של Contact Center AI Platform במהלך שיחת צ'אט או שיחת טלפון. האפשרות הזו יכולה להיות שימושית כשרוצים להחזיר את המשתמש לאפליקציה אחרי שמתקבל אירוע 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. הקריאה החוזרת (callback) של ה-SDK של ה-VoIP מטפלת בזה. |
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. השינוי הזה נכנס לתוקף החל מגרסה 0.31.1 של ה-SDK.
ה-SDK גדול מדי
מה עושים אם קובץ ה-SDK גדול מדי וקשה לעקוב אחריו ב-GitHub
במאמר הזה הם מציעים שתי אפשרויות. מומלץ להשתמש ב-Git lfs.
אם אתם לא משתמשים ב-Bitcode, אתם יכולים להסיר את ה-Bitcode מהקובץ הבינארי. מריצים את הפקודה הזו בתיקייה UJETKit.xcframework.
xcrun bitcode_strip -r UJET -o UJET
dyld: Library not loaded error
מוסיפים את @executable_path/Frameworks ב-Runpath Search Paths מ-Target > Build Settings > Linking.
שליחת אפליקציה ב-iTunes Connect
במהלך תהליך הבדיקה, יכול להיות ש-Apple תציג את השאלה הבאה בגלל מצב הרקע של Voice over IP שמופעל:
האם המשתמשים יכולים לקבל שיחות VoIP באפליקציה?
עונים כן על השאלה.
ההתראה לא זמינה כשמפעילים את ה-SDK
זה מה שכדאי לבדוק:
צריך להשתמש במכשיר אמיתי ולא בסימולטור.
מפעילים את ההתראות ואת מצבי הרקע > יכולת Voice over IP.
אם זה לא עוזר, נסו לבנות את האפליקציה עם פרופיל הקצאה להפצה (Ad-hoc או Apple Store).
בדיקת התראות במכשיר באפליקציית הבדיקה
מכינים את אישור ה-VoIP ואת הטוקן של המכשיר.
בפורטל של פלטפורמת CCAI, עוברים לקטע Push Notification Debug בתפריט Settings > Developer Settings.
אם כבר הגדרתם את האישור ל-APNS, אתם לא צריכים להזין אותו שוב.
מזינים את האישור (אופציונלי), בודקים אם מדובר בארגז חול (sandbox) או לא (אופציונלי) ומזינים את אסימון המכשיר של התראות האפליקציה לבדיקה.
התחלת שיחה חדשה נמשכה יותר מ-30 שניות
בודקים אם אתם מגיבים ל-delegate method של נתונים בהתאמה אישית. צריך להחזיר נתונים חוקיים בהתאמה אישית לפי בקשה, או פשוט להחזיר nil בבלוק ההצלחה.
אפשר להשתמש בקטע הקוד הזה כדוגמה להגדרה:
public func signPayload(_ payload: [AnyHashable: Any]?, payloadType: UjetPayloadType, success: (String?) -> Void, failure: (Error?) -> Void)
{
if payloadType == .customData {
success(nil)
}
}