/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #import "FBSessionCommands.h" #import "FBCapabilities.h" #import "FBClassChainQueryParser.h" #import "FBConfiguration.h" #import "FBExceptions.h" #import "FBLogger.h" #import "FBProtocolHelpers.h" #import "FBRouteRequest.h" #import "FBSession.h" #import "FBSettings.h" #import "FBRuntimeUtils.h" #import "FBActiveAppDetectionPoint.h" #import "FBXCodeCompatibility.h" #import "XCUIApplication+FBHelpers.h" #import "XCUIApplication+FBQuiescence.h" #import "XCUIDevice.h" #import "XCUIDevice+FBHealthCheck.h" #import "XCUIDevice+FBHelpers.h" #import "XCUIApplicationProcessDelay.h" @implementation FBSessionCommands #pragma mark - + (NSArray *)routes { return @[ [[FBRoute POST:@"/url"] respondWithTarget:self action:@selector(handleOpenURL:)], [[FBRoute POST:@"/session"].withoutSession respondWithTarget:self action:@selector(handleCreateSession:)], [[FBRoute POST:@"/wda/apps/launch"] respondWithTarget:self action:@selector(handleSessionAppLaunch:)], [[FBRoute POST:@"/wda/apps/activate"] respondWithTarget:self action:@selector(handleSessionAppActivate:)], [[FBRoute POST:@"/wda/apps/terminate"] respondWithTarget:self action:@selector(handleSessionAppTerminate:)], [[FBRoute POST:@"/wda/apps/state"] respondWithTarget:self action:@selector(handleSessionAppState:)], [[FBRoute GET:@"/wda/apps/list"] respondWithTarget:self action:@selector(handleGetActiveAppsList:)], [[FBRoute GET:@""] respondWithTarget:self action:@selector(handleGetActiveSession:)], [[FBRoute DELETE:@""] respondWithTarget:self action:@selector(handleDeleteSession:)], [[FBRoute GET:@"/status"].withoutSession respondWithTarget:self action:@selector(handleGetStatus:)], // Health check might modify simulator state so it should only be called in-between testing sessions [[FBRoute GET:@"/wda/healthcheck"].withoutSession respondWithTarget:self action:@selector(handleGetHealthCheck:)], // Settings endpoints [[FBRoute GET:@"/appium/settings"] respondWithTarget:self action:@selector(handleGetSettings:)], [[FBRoute POST:@"/appium/settings"] respondWithTarget:self action:@selector(handleSetSettings:)], ]; } #pragma mark - Commands + (id)handleOpenURL:(FBRouteRequest *)request { NSString *urlString = request.arguments[@"url"]; if (!urlString) { return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:@"URL is required" traceback:nil]); } NSString* bundleId = request.arguments[@"bundleId"]; NSNumber* idleTimeoutMs = request.arguments[@"idleTimeoutMs"]; NSError *error; if (nil == bundleId) { if (![XCUIDevice.sharedDevice fb_openUrl:urlString error:&error]) { return FBResponseWithUnknownError(error); } } else { if (![XCUIDevice.sharedDevice fb_openUrl:urlString withApplication:bundleId error:&error]) { return FBResponseWithUnknownError(error); } if (idleTimeoutMs.doubleValue > 0) { XCUIApplication *app = [[XCUIApplication alloc] initWithBundleIdentifier:bundleId]; [app fb_waitUntilStableWithTimeout:FBMillisToSeconds(idleTimeoutMs.doubleValue)]; } } return FBResponseWithOK(); } + (id)handleCreateSession:(FBRouteRequest *)request { if (nil != FBSession.activeSession) { [FBSession.activeSession kill]; } NSDictionary *capabilities; NSError *error; if (![request.arguments[@"capabilities"] isKindOfClass:NSDictionary.class]) { return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:@"'capabilities' is mandatory to create a new session" traceback:nil]); } if (nil == (capabilities = FBParseCapabilities((NSDictionary *)request.arguments[@"capabilities"], &error))) { return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:error.localizedDescription traceback:nil]); } [FBConfiguration resetSessionSettings]; [FBConfiguration setShouldUseTestManagerForVisibilityDetection:[capabilities[FB_CAP_USE_TEST_MANAGER_FOR_VISIBLITY_DETECTION] boolValue]]; if (capabilities[FB_SETTING_USE_COMPACT_RESPONSES]) { [FBConfiguration setShouldUseCompactResponses:[capabilities[FB_SETTING_USE_COMPACT_RESPONSES] boolValue]]; } NSString *elementResponseAttributes = capabilities[FB_SETTING_ELEMENT_RESPONSE_ATTRIBUTES]; if (elementResponseAttributes) { [FBConfiguration setElementResponseAttributes:elementResponseAttributes]; } if (capabilities[FB_CAP_MAX_TYPING_FREQUENCY]) { [FBConfiguration setMaxTypingFrequency:[capabilities[FB_CAP_MAX_TYPING_FREQUENCY] unsignedIntegerValue]]; } if (capabilities[FB_CAP_USE_SINGLETON_TEST_MANAGER]) { [FBConfiguration setShouldUseSingletonTestManager:[capabilities[FB_CAP_USE_SINGLETON_TEST_MANAGER] boolValue]]; } if (capabilities[FB_CAP_DISABLE_AUTOMATIC_SCREENSHOTS]) { if ([capabilities[FB_CAP_DISABLE_AUTOMATIC_SCREENSHOTS] boolValue]) { [FBConfiguration disableScreenshots]; } else { [FBConfiguration enableScreenshots]; } } if (capabilities[FB_CAP_SHOULD_TERMINATE_APP]) { [FBConfiguration setShouldTerminateApp:[capabilities[FB_CAP_SHOULD_TERMINATE_APP] boolValue]]; } NSNumber *delay = capabilities[FB_CAP_EVENT_LOOP_IDLE_DELAY_SEC]; if ([delay doubleValue] > 0.0) { [XCUIApplicationProcessDelay setEventLoopHasIdledDelay:[delay doubleValue]]; } else { [XCUIApplicationProcessDelay disableEventLoopDelay]; } if (nil != capabilities[FB_SETTING_WAIT_FOR_IDLE_TIMEOUT]) { FBConfiguration.waitForIdleTimeout = [capabilities[FB_SETTING_WAIT_FOR_IDLE_TIMEOUT] doubleValue]; } if (nil == capabilities[FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE] || [capabilities[FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE] boolValue]) { [FBConfiguration forceSimulatorSoftwareKeyboardPresence]; } NSString *bundleID = capabilities[FB_CAP_BUNDLE_ID]; NSString *initialUrl = capabilities[FB_CAP_INITIAL_URL]; XCUIApplication *app = nil; if (bundleID != nil) { app = [[XCUIApplication alloc] initWithBundleIdentifier:bundleID]; BOOL forceAppLaunch = YES; if (nil != capabilities[FB_CAP_FORCE_APP_LAUNCH]) { forceAppLaunch = [capabilities[FB_CAP_FORCE_APP_LAUNCH] boolValue]; } XCUIApplicationState appState = app.state; BOOL isAppRunning = appState >= XCUIApplicationStateRunningBackground; if (!isAppRunning || (isAppRunning && forceAppLaunch)) { app.fb_shouldWaitForQuiescence = nil == capabilities[FB_CAP_SHOULD_WAIT_FOR_QUIESCENCE] || [capabilities[FB_CAP_SHOULD_WAIT_FOR_QUIESCENCE] boolValue]; app.launchArguments = (NSArray *)capabilities[FB_CAP_ARGUMENTS] ?: @[]; app.launchEnvironment = (NSDictionary *)capabilities[FB_CAP_ENVIRNOMENT] ?: @{}; if (nil != initialUrl) { if (app.running) { [app terminate]; } id errorResponse = [self openDeepLink:initialUrl withApplication:bundleID timeout:capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]]; if (nil != errorResponse) { return errorResponse; } } else { NSTimeInterval defaultTimeout = _XCTApplicationStateTimeout(); if (nil != capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]) { _XCTSetApplicationStateTimeout([capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC] doubleValue]); } @try { [app launch]; } @catch (NSException *e) { return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:e.reason traceback:nil]); } @finally { if (nil != capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]) { _XCTSetApplicationStateTimeout(defaultTimeout); } } } if (!app.running) { NSString *errorMsg = [NSString stringWithFormat:@"Cannot launch %@ application. Make sure the correct bundle identifier has been provided in capabilities and check the device log for possible crash report occurrences", bundleID]; return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg traceback:nil]); } } else if (appState == XCUIApplicationStateRunningBackground && !forceAppLaunch) { if (nil != initialUrl) { id errorResponse = [self openDeepLink:initialUrl withApplication:bundleID timeout:nil]; if (nil != errorResponse) { return errorResponse; } } else { [app activate]; } } } if (nil != initialUrl && nil == bundleID) { id errorResponse = [self openDeepLink:initialUrl withApplication:nil timeout:capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]]; if (nil != errorResponse) { return errorResponse; } } if (capabilities[FB_SETTING_DEFAULT_ALERT_ACTION]) { [FBSession initWithApplication:app defaultAlertAction:(id)capabilities[FB_SETTING_DEFAULT_ALERT_ACTION]]; } else { [FBSession initWithApplication:app]; } if (nil != capabilities[FB_CAP_USE_NATIVE_CACHING_STRATEGY]) { FBSession.activeSession.useNativeCachingStrategy = [capabilities[FB_CAP_USE_NATIVE_CACHING_STRATEGY] boolValue]; } return FBResponseWithObject(FBSessionCommands.sessionInformation); } + (id)handleSessionAppLaunch:(FBRouteRequest *)request { [request.session launchApplicationWithBundleId:(id)request.arguments[@"bundleId"] shouldWaitForQuiescence:request.arguments[@"shouldWaitForQuiescence"] arguments:request.arguments[@"arguments"] environment:request.arguments[@"environment"]]; return FBResponseWithOK(); } + (id)handleSessionAppActivate:(FBRouteRequest *)request { [request.session activateApplicationWithBundleId:(id)request.arguments[@"bundleId"]]; return FBResponseWithOK(); } + (id)handleSessionAppTerminate:(FBRouteRequest *)request { BOOL result = [request.session terminateApplicationWithBundleId:(id)request.arguments[@"bundleId"]]; return FBResponseWithObject(@(result)); } + (id)handleSessionAppState:(FBRouteRequest *)request { NSUInteger state = [request.session applicationStateWithBundleId:(id)request.arguments[@"bundleId"]]; return FBResponseWithObject(@(state)); } + (id)handleGetActiveAppsList:(FBRouteRequest *)request { return FBResponseWithObject([XCUIApplication fb_activeAppsInfo]); } + (id)handleGetActiveSession:(FBRouteRequest *)request { return FBResponseWithObject(FBSessionCommands.sessionInformation); } + (id)handleDeleteSession:(FBRouteRequest *)request { [request.session kill]; return FBResponseWithOK(); } + (id)handleGetStatus:(FBRouteRequest *)request { // For updatedWDABundleId capability by Appium NSString *productBundleIdentifier = @"com.facebook.WebDriverAgentRunner"; NSString *envproductBundleIdentifier = NSProcessInfo.processInfo.environment[@"WDA_PRODUCT_BUNDLE_IDENTIFIER"]; if (envproductBundleIdentifier && [envproductBundleIdentifier length] != 0) { productBundleIdentifier = NSProcessInfo.processInfo.environment[@"WDA_PRODUCT_BUNDLE_IDENTIFIER"]; } NSMutableDictionary *buildInfo = [NSMutableDictionary dictionaryWithDictionary:@{ @"time" : [self.class buildTimestamp], @"productBundleIdentifier" : productBundleIdentifier, }]; NSString *upgradeTimestamp = NSProcessInfo.processInfo.environment[@"UPGRADE_TIMESTAMP"]; if (nil != upgradeTimestamp && upgradeTimestamp.length > 0) { [buildInfo setObject:upgradeTimestamp forKey:@"upgradedAt"]; } NSDictionary *infoDict = [[NSBundle bundleForClass:self.class] infoDictionary]; NSString *version = [infoDict objectForKey:@"CFBundleShortVersionString"]; if (nil != version) { [buildInfo setObject:version forKey:@"version"]; } return FBResponseWithObject( @{ @"ready" : @YES, @"message" : @"WebDriverAgent is ready to accept commands", @"state" : @"success", @"os" : @{ @"name" : [[UIDevice currentDevice] systemName], @"version" : [[UIDevice currentDevice] systemVersion], @"sdkVersion": FBSDKVersion() ?: @"unknown", @"testmanagerdVersion": @(FBTestmanagerdVersion()), }, @"ios" : @{ #if TARGET_OS_SIMULATOR @"simulatorVersion" : [[UIDevice currentDevice] systemVersion], #endif @"ip" : [XCUIDevice sharedDevice].fb_wifiIPAddress ?: [NSNull null] }, @"build" : buildInfo.copy, @"device": [self.class deviceNameByUserInterfaceIdiom:[UIDevice currentDevice].userInterfaceIdiom] } ); } + (id)handleGetHealthCheck:(FBRouteRequest *)request { if (![[XCUIDevice sharedDevice] fb_healthCheckWithApplication:[XCUIApplication fb_activeApplication]]) { return FBResponseWithUnknownErrorFormat(@"Health check failed"); } return FBResponseWithOK(); } + (id)handleGetSettings:(FBRouteRequest *)request { return FBResponseWithObject( @{ FB_SETTING_USE_COMPACT_RESPONSES: @([FBConfiguration shouldUseCompactResponses]), FB_SETTING_ELEMENT_RESPONSE_ATTRIBUTES: [FBConfiguration elementResponseAttributes], FB_SETTING_MJPEG_SERVER_SCREENSHOT_QUALITY: @([FBConfiguration mjpegServerScreenshotQuality]), FB_SETTING_MJPEG_SERVER_FRAMERATE: @([FBConfiguration mjpegServerFramerate]), FB_SETTING_MJPEG_SCALING_FACTOR: @([FBConfiguration mjpegScalingFactor]), FB_SETTING_MJPEG_FIX_ORIENTATION: @([FBConfiguration mjpegShouldFixOrientation]), FB_SETTING_SCREENSHOT_QUALITY: @([FBConfiguration screenshotQuality]), FB_SETTING_KEYBOARD_AUTOCORRECTION: @([FBConfiguration keyboardAutocorrection]), FB_SETTING_KEYBOARD_PREDICTION: @([FBConfiguration keyboardPrediction]), FB_SETTING_SNAPSHOT_MAX_DEPTH: @([FBConfiguration snapshotMaxDepth]), FB_SETTING_USE_FIRST_MATCH: @([FBConfiguration useFirstMatch]), FB_SETTING_WAIT_FOR_IDLE_TIMEOUT: @([FBConfiguration waitForIdleTimeout]), FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT: @([FBConfiguration animationCoolOffTimeout]), FB_SETTING_BOUND_ELEMENTS_BY_INDEX: @([FBConfiguration boundElementsByIndex]), FB_SETTING_REDUCE_MOTION: @([FBConfiguration reduceMotionEnabled]), FB_SETTING_DEFAULT_ACTIVE_APPLICATION: request.session.defaultActiveApplication, FB_SETTING_ACTIVE_APP_DETECTION_POINT: FBActiveAppDetectionPoint.sharedInstance.stringCoordinates, FB_SETTING_INCLUDE_NON_MODAL_ELEMENTS: @([FBConfiguration includeNonModalElements]), FB_SETTING_ACCEPT_ALERT_BUTTON_SELECTOR: FBConfiguration.acceptAlertButtonSelector, FB_SETTING_DISMISS_ALERT_BUTTON_SELECTOR: FBConfiguration.dismissAlertButtonSelector, FB_SETTING_AUTO_CLICK_ALERT_SELECTOR: FBConfiguration.autoClickAlertSelector, FB_SETTING_DEFAULT_ALERT_ACTION: request.session.defaultAlertAction ?: @"", FB_SETTING_MAX_TYPING_FREQUENCY: @([FBConfiguration maxTypingFrequency]), FB_SETTING_RESPECT_SYSTEM_ALERTS: @([FBConfiguration shouldRespectSystemAlerts]), FB_SETTING_USE_CLEAR_TEXT_SHORTCUT: @([FBConfiguration useClearTextShortcut]), FB_SETTING_INCLUDE_HITTABLE_IN_PAGE_SOURCE: @([FBConfiguration includeHittableInPageSource]), FB_SETTING_INCLUDE_NATIVE_FRAME_IN_PAGE_SOURCE: @([FBConfiguration includeNativeFrameInPageSource]), FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE: @([FBConfiguration includeMinMaxValueInPageSource]), FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE: @([FBConfiguration limitXpathContextScope]), #if !TARGET_OS_TV FB_SETTING_SCREENSHOT_ORIENTATION: [FBConfiguration humanReadableScreenshotOrientation], #endif } ); } // TODO if we get lots more settings, handling them with a series of if-statements will be unwieldy // and this should be refactored + (id)handleSetSettings:(FBRouteRequest *)request { NSDictionary* settings = request.arguments[@"settings"]; if (nil != [settings objectForKey:FB_SETTING_USE_COMPACT_RESPONSES]) { [FBConfiguration setShouldUseCompactResponses:[[settings objectForKey:FB_SETTING_USE_COMPACT_RESPONSES] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_ELEMENT_RESPONSE_ATTRIBUTES]) { [FBConfiguration setElementResponseAttributes:(NSString *)[settings objectForKey:FB_SETTING_ELEMENT_RESPONSE_ATTRIBUTES]]; } if (nil != [settings objectForKey:FB_SETTING_MJPEG_SERVER_SCREENSHOT_QUALITY]) { [FBConfiguration setMjpegServerScreenshotQuality:[[settings objectForKey:FB_SETTING_MJPEG_SERVER_SCREENSHOT_QUALITY] unsignedIntegerValue]]; } if (nil != [settings objectForKey:FB_SETTING_MJPEG_SERVER_FRAMERATE]) { [FBConfiguration setMjpegServerFramerate:[[settings objectForKey:FB_SETTING_MJPEG_SERVER_FRAMERATE] unsignedIntegerValue]]; } if (nil != [settings objectForKey:FB_SETTING_SCREENSHOT_QUALITY]) { [FBConfiguration setScreenshotQuality:[[settings objectForKey:FB_SETTING_SCREENSHOT_QUALITY] unsignedIntegerValue]]; } if (nil != [settings objectForKey:FB_SETTING_MJPEG_SCALING_FACTOR]) { [FBConfiguration setMjpegScalingFactor:[[settings objectForKey:FB_SETTING_MJPEG_SCALING_FACTOR] floatValue]]; } if (nil != [settings objectForKey:FB_SETTING_MJPEG_FIX_ORIENTATION]) { [FBConfiguration setMjpegShouldFixOrientation:[[settings objectForKey:FB_SETTING_MJPEG_FIX_ORIENTATION] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_KEYBOARD_AUTOCORRECTION]) { [FBConfiguration setKeyboardAutocorrection:[[settings objectForKey:FB_SETTING_KEYBOARD_AUTOCORRECTION] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_KEYBOARD_PREDICTION]) { [FBConfiguration setKeyboardPrediction:[[settings objectForKey:FB_SETTING_KEYBOARD_PREDICTION] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_RESPECT_SYSTEM_ALERTS]) { [FBConfiguration setShouldRespectSystemAlerts:[[settings objectForKey:FB_SETTING_RESPECT_SYSTEM_ALERTS] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_SNAPSHOT_MAX_DEPTH]) { [FBConfiguration setSnapshotMaxDepth:[[settings objectForKey:FB_SETTING_SNAPSHOT_MAX_DEPTH] intValue]]; } if (nil != [settings objectForKey:FB_SETTING_USE_FIRST_MATCH]) { [FBConfiguration setUseFirstMatch:[[settings objectForKey:FB_SETTING_USE_FIRST_MATCH] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_BOUND_ELEMENTS_BY_INDEX]) { [FBConfiguration setBoundElementsByIndex:[[settings objectForKey:FB_SETTING_BOUND_ELEMENTS_BY_INDEX] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_REDUCE_MOTION]) { [FBConfiguration setReduceMotionEnabled:[[settings objectForKey:FB_SETTING_REDUCE_MOTION] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_DEFAULT_ACTIVE_APPLICATION]) { request.session.defaultActiveApplication = (NSString *)[settings objectForKey:FB_SETTING_DEFAULT_ACTIVE_APPLICATION]; } if (nil != [settings objectForKey:FB_SETTING_ACTIVE_APP_DETECTION_POINT]) { NSError *error; if (![FBActiveAppDetectionPoint.sharedInstance setCoordinatesWithString:(NSString *)[settings objectForKey:FB_SETTING_ACTIVE_APP_DETECTION_POINT] error:&error]) { return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:error.localizedDescription traceback:nil]); } } if (nil != [settings objectForKey:FB_SETTING_INCLUDE_NON_MODAL_ELEMENTS]) { if ([XCUIElement fb_supportsNonModalElementsInclusion]) { [FBConfiguration setIncludeNonModalElements:[[settings objectForKey:FB_SETTING_INCLUDE_NON_MODAL_ELEMENTS] boolValue]]; } else { [FBLogger logFmt:@"'%@' settings value cannot be assigned, because non modal elements inclusion is not supported by the current iOS SDK", FB_SETTING_INCLUDE_NON_MODAL_ELEMENTS]; } } if (nil != [settings objectForKey:FB_SETTING_ACCEPT_ALERT_BUTTON_SELECTOR]) { [FBConfiguration setAcceptAlertButtonSelector:(NSString *)[settings objectForKey:FB_SETTING_ACCEPT_ALERT_BUTTON_SELECTOR]]; } if (nil != [settings objectForKey:FB_SETTING_DISMISS_ALERT_BUTTON_SELECTOR]) { [FBConfiguration setDismissAlertButtonSelector:(NSString *)[settings objectForKey:FB_SETTING_DISMISS_ALERT_BUTTON_SELECTOR]]; } if (nil != [settings objectForKey:FB_SETTING_AUTO_CLICK_ALERT_SELECTOR]) { FBCommandStatus *status = [self.class configureAutoClickAlertWithSelector:settings[FB_SETTING_AUTO_CLICK_ALERT_SELECTOR] forSession:request.session]; if (status.hasError) { return FBResponseWithStatus(status); } } if (nil != [settings objectForKey:FB_SETTING_WAIT_FOR_IDLE_TIMEOUT]) { [FBConfiguration setWaitForIdleTimeout:[[settings objectForKey:FB_SETTING_WAIT_FOR_IDLE_TIMEOUT] doubleValue]]; } if (nil != [settings objectForKey:FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT]) { [FBConfiguration setAnimationCoolOffTimeout:[[settings objectForKey:FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT] doubleValue]]; } if ([[settings objectForKey:FB_SETTING_DEFAULT_ALERT_ACTION] isKindOfClass:NSString.class]) { request.session.defaultAlertAction = [settings[FB_SETTING_DEFAULT_ALERT_ACTION] lowercaseString]; } if (nil != [settings objectForKey:FB_SETTING_MAX_TYPING_FREQUENCY]) { [FBConfiguration setMaxTypingFrequency:[[settings objectForKey:FB_SETTING_MAX_TYPING_FREQUENCY] unsignedIntegerValue]]; } if (nil != [settings objectForKey:FB_SETTING_USE_CLEAR_TEXT_SHORTCUT]) { [FBConfiguration setUseClearTextShortcut:[[settings objectForKey:FB_SETTING_USE_CLEAR_TEXT_SHORTCUT] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_INCLUDE_HITTABLE_IN_PAGE_SOURCE]) { [FBConfiguration setIncludeHittableInPageSource:[[settings objectForKey:FB_SETTING_INCLUDE_HITTABLE_IN_PAGE_SOURCE] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_INCLUDE_NATIVE_FRAME_IN_PAGE_SOURCE]) { [FBConfiguration setIncludeNativeFrameInPageSource:[[settings objectForKey:FB_SETTING_INCLUDE_NATIVE_FRAME_IN_PAGE_SOURCE] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE]) { [FBConfiguration setIncludeMinMaxValueInPageSource:[[settings objectForKey:FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE] boolValue]]; } if (nil != [settings objectForKey:FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE]) { [FBConfiguration setLimitXpathContextScope:[[settings objectForKey:FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE] boolValue]]; } #if !TARGET_OS_TV if (nil != [settings objectForKey:FB_SETTING_SCREENSHOT_ORIENTATION]) { NSError *error; if (![FBConfiguration setScreenshotOrientation:(NSString *)[settings objectForKey:FB_SETTING_SCREENSHOT_ORIENTATION] error:&error]) { return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:error.localizedDescription traceback:nil]); } } #endif return [self handleGetSettings:request]; } #pragma mark - Helpers + (FBCommandStatus *)configureAutoClickAlertWithSelector:(NSString *)selector forSession:(FBSession *)session { if (0 == [selector length]) { [FBConfiguration setAutoClickAlertSelector:selector]; [session disableAlertsMonitor]; return [FBCommandStatus ok]; } NSError *error; FBClassChain *parsedChain = [FBClassChainQueryParser parseQuery:selector error:&error]; if (nil == parsedChain) { return [FBCommandStatus invalidSelectorErrorWithMessage:error.localizedDescription traceback:nil]; } [FBConfiguration setAutoClickAlertSelector:selector]; [session enableAlertsMonitor]; return [FBCommandStatus ok]; } + (NSString *)buildTimestamp { return [NSString stringWithFormat:@"%@ %@", [NSString stringWithUTF8String:__DATE__], [NSString stringWithUTF8String:__TIME__] ]; } /** Return current session information. This response does not have any active application information. */ + (NSDictionary *)sessionInformation { return @{ @"sessionId" : [FBSession activeSession].identifier ?: NSNull.null, @"capabilities" : FBSessionCommands.currentCapabilities }; } /* Return the device kind as lower case */ + (NSString *)deviceNameByUserInterfaceIdiom:(UIUserInterfaceIdiom) userInterfaceIdiom { if (userInterfaceIdiom == UIUserInterfaceIdiomPad) { return @"ipad"; } else if (userInterfaceIdiom == UIUserInterfaceIdiomTV) { return @"apple tv"; } else if (userInterfaceIdiom == UIUserInterfaceIdiomPhone) { return @"iphone"; } // CarPlay, Mac, Vision UI or unknown are possible return @"Unknown"; } + (NSDictionary *)currentCapabilities { return @{ @"device": [self.class deviceNameByUserInterfaceIdiom:[UIDevice currentDevice].userInterfaceIdiom], @"sdkVersion": [[UIDevice currentDevice] systemVersion] }; } +(nullable id)openDeepLink:(NSString *)initialUrl withApplication:(nullable NSString *)bundleID timeout:(nullable NSNumber *)timeout { NSError *openError; NSTimeInterval defaultTimeout = _XCTApplicationStateTimeout(); if (nil != timeout) { _XCTSetApplicationStateTimeout([timeout doubleValue]); } @try { BOOL result = nil == bundleID ? [XCUIDevice.sharedDevice fb_openUrl:initialUrl error:&openError] : [XCUIDevice.sharedDevice fb_openUrl:initialUrl withApplication:(id)bundleID error:&openError]; if (result) { return nil; } NSString *errorMsg = [NSString stringWithFormat:@"Cannot open the URL %@ with the %@ application. Original error: %@", initialUrl, bundleID ?: @"default", openError.localizedDescription]; return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg traceback:nil]); } @finally { if (nil != timeout) { _XCTSetApplicationStateTimeout(defaultTimeout); } } } @end