Contact Center AI Platform (CCAI Platform) מספקת שילוב עם אפליקציות לנייד באמצעות מספר שיטות, כולל React. במאמר הזה מוסבר איך לשלב את React Native ל-iOS.
לפני שמבצעים את ההוראות הספציפיות ל-Android, צריך להשלים את ההוראות במאמר React Native for Mobile SDK.
שילוב של ערכת SDK באמצעות CocoaPods
כדי לשלב את ה-SDK לנייד באמצעות CocoaPods, פותחים את Podfile ומוסיפים את קשרי התלות הבאים ליעד:
UJETPodspec = { :podspec => 'https://sdk.ujet.co/ios/UJET.podspec' } pod 'UJET', UJETPodspec pod 'UJET/Cobrowse', UJETPodspecמריצים את הפקודה
pod-installמהתיקייה ios או את הפקודה npx pod-install מהתיקייה app כדי להתקין את התלויות.
הגדרת התראות
כדי להגדיר התראות פוש ב-SDK לנייד, פועלים לפי השלבים הבאים:
פותחים את פרויקט Xcode.
פותחים את הקובץ שתואם ל-
RCTAppDelegateומוסיפים את הקוד הבא:#import <UJETKit/UJETKit.h> #import <PushKit/PushKit.h> @interface AppDelegate() <PKPushRegistryDelegate> @end - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { PKPushRegistry *voipRegistry = [[PKPushRegistry alloc] initWithQueue: dispatch_get_main_queue()]; voipRegistry.delegate = self; voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; } #pragma mark PushKit - (NSString *)tokenFromData:(NSData *)data { const void *bytes = [data bytes]; const unsigned char *d = (const unsigned char *)bytes; NSMutableString *token = [NSMutableString string]; for (NSUInteger i = 0; i < [data length]; ++i) { [token appendFormat:@"%02X", d[i]]; } return [token lowercaseString]; } - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type { NSLog(@"voip token: %@", [self tokenFromData:credentials.token]); if ([type isEqual:PKPushTypeVoIP]) { [UJET updatePushToken:credentials.token type:UjetPushTypeVoIP]; } } - (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type { if ([type isEqual:PKPushTypeVoIP]) { [UJET updatePushToken:nil type:UjetPushTypeVoIP]; } } - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { if ([type isEqual:PKPushTypeVoIP] && payload.dictionaryPayload[@"ujet"]) { [UJET receivedNotification:payload.dictionaryPayload completion:completion]; } else { completion(); } } #pragma mark APNS - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSLog(@"apns token: %@", [self tokenFromData:deviceToken]); [UJET updatePushToken:deviceToken type:UjetPushTypeAPN]; } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error { [UJET updatePushToken:nil type:UjetPushTypeAPN]; } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { if (userInfo[@"ujet"]) { [UJET receivedNotification:userInfo completion:nil]; } else { // handle your notifications } }ב-Swift:
import UJETKit import PushKit func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let voipRegistry = PKPushRegistry(queue: DispatchQueue.main) voipRegistry.desiredPushTypes = Set([PKPushType.voIP]) voipRegistry.delegate = self } // MARK: Push Notifications extension AppDelegate { 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)) UJET.updatePushToken(deviceToken, type: .APN) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { UJET.updatePushToken(nil, type: .APN) } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { if userInfo["ujet"] != nil { UJET.receivedNotification(userInfo, completion: nil) } else { // handle your notifications } } } // MARK: PushKit extension AppDelegate: PKPushRegistryDelegate { func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { print("voip token: ", tokenFromData(data: credentials.token)) if type == .voIP { UJET.updatePushToken(credentials.token, type: .voIP) } } func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { if type == .voIP { UJET.updatePushToken(nil, type: .voIP) } } func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { if type == .voIP && payload.dictionaryPayload["ujet"] != nil { UJET.receivedNotification(payload.dictionaryPayload, completion: completion) } else { completion() } } }בוחרים את הקובץ .xcodeproj ואז את יעד האפליקציה. מוסיפים את PushKit.framework לקטע Frameworks, Libraries, and Embedded Content (מסגרות, ספריות ותוכן מוטמע).
הגדרת קישורי עומק
כדי לספק תמיכה בקישורי עומק באפליקציה לנייד, משתמשים בקוד הבא:
#pragma mark Deep Link
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
if ([url.scheme isEqualToString:@"ujet"]) {
return [self handleRouting:url];
}
return NO;
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler {
// Universal links
if ([NSUserActivityTypeBrowsingWeb isEqualToString:userActivity.activityType]) {
return [self handleRouting:userActivity.webpageURL];
} else if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) {
// Open app from Call history
[UJET startWithOptions:nil];
return YES;
}
return NO;
}
- (BOOL)handleRouting:(NSURL *)url {
NSArray *availableSchema = @[
@"ujetrn", // TODO: Change to your custom URL scheme. Config from Portal > Developer Settings > Mobile App > Enable Send SMS to Download App > iOS App > URL
@"https" // TODO: or your universal link
];
NSArray *availableHostAndPath = @[
@"call", // custom URL scheme
@"ujet.cx/app" // universal link
];
if (![availableSchema containsObject:url.scheme]) {
return NO;
}
NSString *hostAndPath = [NSString stringWithFormat:@"%@%@", url.host, url.path];
if (![availableHostAndPath containsObject:hostAndPath]) {
return NO;
}
// ujetrn://call?call_id={call_id}&nonce={nonce}
// https://ujet.co/app?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];
return YES;
}
- (NSString *)valueForKey:(NSString *)key fromQueryItems:(NSArray *)queryItems {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", key];
NSURLQueryItem *queryItem = [[queryItems
filteredArrayUsingPredicate:predicate]
firstObject];
return queryItem.value;
}
- (BOOL)isValidCallId:(NSString *)callId {
if (callId.length == 0) {
return NO;
}
NSCharacterSet *nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
NSRange r = [callId rangeOfCharacterFromSet: nonNumbers];
return r.location == NSNotFound;
}
ב-Swift:
// MARK: Deep Link
extension AppDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
print("Open app with url: \(url.absoluteString)")
return self.handleRouting(url)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Universal links
if NSUserActivityTypeBrowsingWeb == userActivity.activityType {
return self.handleRouting(userActivity.webpageURL!)
} else if userActivity.activityType == "INStartAudioCallIntent" {
// Open app from Call history
let model = DataController.shared.startOptions()
let startOptions = StartOptionsController().convert(model)
UJET.start(with: startOptions)
return true
}
return false
}
func handleRouting(_ url: URL) -> Bool {
let availableSchema = [
"ujetrn", // TODO: Change to your custom URL scheme. Config from Portal > Developer Settings > Mobile App > Enable Send SMS to Download App > iOS App > URL
"https" // universal link
]
let availableHostAndPath = [
"call", // custom URL scheme
"ujet.cx/app", // universal link,
]
if !(availableSchema.contains(url.scheme ?? "")) {
return false
}
let hostAndPath = String.init(format: "%@%@", url.host ?? "", url.path)
if !(availableHostAndPath.contains(hostAndPath)) {
return false
}
// ujet://call?call_id={call_id}&nonce={nonce}
// https://ujet.co/app?call_id={call_id}&nonce={nonce}
let urlComponents: URLComponents? = URLComponents(url: url, resolvingAgainstBaseURL: false)
let queryItems = urlComponents?.queryItems
let callId = value(forKey: "call_id", fromQueryItems: queryItems)
// validate call ID
if !isValidCallId(callId) {
return false
}
guard let nonce = value(forKey: "nonce", fromQueryItems: queryItems) else {
return false
}
let options = UJETStartOptions.init(callId: callId!, nonce: nonce)
UJET.start(with: options)
return true
}
func value(forKey key: String?, fromQueryItems queryItems: [URLQueryItem]?) -> String? {
let predicate = NSPredicate(format: "name=%@", key ?? "")
let filtered = (queryItems as NSArray?)?.filtered(using: predicate) as? [URLQueryItem]
let queryItem: URLQueryItem? = filtered?.first
return queryItem?.value
}
func isValidCallId(_ callId: String?) -> Bool {
if (callId ?? "").isEmpty {
return false
}
let nonNumbers = CharacterSet.decimalDigits.inverted
let r = callId?.rangeOfCharacter(from: nonNumbers)
return r == nil
}
}
הגדרות היעד
בקטע הזה מפורטות היכולות ומידע נוסף שנדרש להגדרת היעד.
יכולות
הודעות שנשלחות מהאפליקציה
מצבי רקע (צריך לסמן את הפריטים שלמטה):
אודיו ו-AirPlay
פרוטוקול VoIP
מידע
מוסיפים את המפתחות הבאים עם התיאור:
NSMicrophoneUsageDescription
NSCameraUsageDescription
NSPhotoLibraryUsageDescription
NSFaceIDUsageDescription