FBSDKTVOSKit/FBSDKTVOSKit/FBSDKDeviceLoginViewController.m (190 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. */ #import "FBSDKDeviceLoginViewController.h" #import <FBSDKLoginKit/FBSDKDeviceLoginManager.h> #import <FBSDKLoginKit/FBSDKDeviceLoginManagerDelegate.h> @interface FBSDKDeviceLoginViewController () <FBSDKDeviceLoginManagerDelegate> @property (nonatomic) FBSDKDeviceLoginManager *loginManager; @property (nonatomic) BOOL isRetry; @end @implementation FBSDKDeviceLoginViewController - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self _cancel]; } - (void)viewDidLoad { [super viewDidLoad]; [self _initializeLoginManager]; } - (void)dealloc { _loginManager.delegate = nil; _loginManager = nil; } #pragma mark - FBSDKDeviceLoginManagerDelegate - (void)deviceLoginManager:(FBSDKDeviceLoginManager *)loginManager startedWithCodeInfo:(FBSDKDeviceLoginCodeInfo *)codeInfo { ((FBSDKDeviceDialogView *)self.view).confirmationCode = codeInfo.loginCode; } - (void)deviceLoginManager:(FBSDKDeviceLoginManager *)loginManager completedWithResult:(FBSDKDeviceLoginManagerResult *)result error:(NSError *)error { // Go ahead and clear the delegate to avoid double messaging (i.e., since we're dismissing // ourselves we don't want a didCancel (from viewDidDisappear) then didFinish. id<FBSDKDeviceLoginViewControllerDelegate> delegate = self.delegate; self.delegate = nil; FBSDKServerConfigurationProvider *provider = [FBSDKServerConfigurationProvider new]; NSUInteger smartLoginOptions = [provider cachedSmartLoginOptions]; NSUInteger smartLoginRequireConfirmation = 1 << 1; FBSDKAccessToken *token = result.accessToken; BOOL requireConfirm = ((smartLoginOptions & smartLoginRequireConfirmation) && (token != nil) && !_isRetry); if (requireConfirm) { FBSDKGraphRequest *graphRequest = [[FBSDKGraphRequest alloc] initWithGraphPath:@"me" parameters:@{ @"fields" : @"name" } tokenString:token.tokenString version:nil HTTPMethod:@"GET"]; [graphRequest startWithCompletion:^(id<FBSDKGraphRequestConnecting> connection, id graphResult, NSError *graphError) { dispatch_async(dispatch_get_main_queue(), ^{ [self _presentConfirmationForDelegate:delegate token:result.accessToken name:graphResult[@"name"] ?: token.userID]; }); }]; } else if ([self isNetworkError:error]) { NSString *networkErrorMessage = NSLocalizedStringWithDefaultValue( @"LoginError.SystemAccount.Network", @"FacebookSDK", [FBSDKInternalUtility.sharedUtility bundleForStrings], @"Unable to connect to Facebook. Check your network connection and try again.", @"The user facing error message when the Accounts framework encounters a network error." ); UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:networkErrorMessage preferredStyle:UIAlertControllerStyleAlert]; NSString *localizedOK = NSLocalizedStringWithDefaultValue( @"ErrorRecovery.Alert.OK", @"FacebookSDK", [FBSDKInternalUtility.sharedUtility bundleForStrings], @"OK", @"The title of the label to dismiss the alert when presenting user facing error messages" ); UIAlertAction *okAction = [UIAlertAction actionWithTitle:localizedOK style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull action) { [self dismissViewControllerAnimated:YES completion:^{ [delegate deviceLoginViewController:self didFailWithError:error]; }]; }]; [alertController addAction:okAction]; [self presentViewController:alertController animated:YES completion:nil]; } else { [self dismissViewControllerAnimated:YES completion:^{ if (result.isCancelled) { [self _cancel]; } else if (token != nil) { [self _notifySuccessForDelegate:delegate token:token]; } else { [delegate deviceLoginViewController:self didFailWithError:error]; } }]; } } - (BOOL)isNetworkError:(NSError *)error { NSError *innerError = error.userInfo[NSUnderlyingErrorKey]; if (innerError && [self isNetworkError:innerError]) { return YES; } switch (error.code) { case NSURLErrorTimedOut: case NSURLErrorCannotFindHost: case NSURLErrorCannotConnectToHost: case NSURLErrorNetworkConnectionLost: case NSURLErrorDNSLookupFailed: case NSURLErrorNotConnectedToInternet: case NSURLErrorInternationalRoamingOff: case NSURLErrorCallIsActive: case NSURLErrorDataNotAllowed: return YES; default: return NO; } } #pragma mark - Private impl - (void)_notifySuccessForDelegate:(id<FBSDKDeviceLoginViewControllerDelegate>)delegate token:(FBSDKAccessToken *)token { FBSDKAccessToken.currentAccessToken = token; [delegate deviceLoginViewControllerDidFinish:self]; } - (void)_presentConfirmationForDelegate:(id<FBSDKDeviceLoginViewControllerDelegate>)delegate token:(FBSDKAccessToken *)token name:(NSString *)name { NSString *title = NSLocalizedStringWithDefaultValue( @"SmartLogin.ConfirmationTitle", @"FacebookSDK", [FBSDKInternalUtility.sharedUtility bundleForStrings], @"Confirm Login", @"The title for the alert when smart login requires confirmation" ); NSString *cancelTitle = NSLocalizedStringWithDefaultValue( @"SmartLogin.NotYou", @"FacebookSDK", [FBSDKInternalUtility.sharedUtility bundleForStrings], @"Not you?", @"The cancel label for the alert when smart login requires confirmation" ); NSString *continueTitleFormatString = NSLocalizedStringWithDefaultValue( @"SmartLogin.Continue", @"FacebookSDK", [FBSDKInternalUtility.sharedUtility bundleForStrings], @"Continue as %@", @"The format string to continue as <name> for the alert when smart login requires confirmation" ); NSString *continueTitle = [NSString stringWithFormat:continueTitleFormatString, name]; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:title preferredStyle:UIAlertControllerStyleActionSheet]; [alertController addAction:[UIAlertAction actionWithTitle:continueTitle style:UIAlertActionStyleDestructive handler:^(UIAlertAction *_Nonnull action) { [self dismissViewControllerAnimated:YES completion:^{ [self _notifySuccessForDelegate:delegate token:token]; }]; }]]; [alertController addAction:[UIAlertAction actionWithTitle:cancelTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull action) { self->_isRetry = YES; FBSDKDeviceDialogView *view = [[FBSDKDeviceDialogView alloc] initWithFrame:self.view.frame]; view.delegate = self; self.view = view; [self.view setNeedsDisplay]; [self _initializeLoginManager]; // reconnect delegate before since now // we are not dismissing. self.delegate = delegate; }]]; [self presentViewController:alertController animated:YES completion:NULL]; } - (void)_initializeLoginManager { // clear any existing login manager _loginManager.delegate = nil; [_loginManager cancel]; _loginManager = nil; FBSDKServerConfigurationProvider *provider = [FBSDKServerConfigurationProvider new]; NSUInteger smartLoginOptions = [provider cachedSmartLoginOptions]; NSUInteger smartLoginRequireConfirmation = 1 << 0; BOOL enableSmartLogin = (!_isRetry && (smartLoginOptions & smartLoginRequireConfirmation)); _loginManager = [[FBSDKDeviceLoginManager alloc] initWithPermissions:_permissions enableSmartLogin:enableSmartLogin]; _loginManager.delegate = self; _loginManager.redirectURL = self.redirectURL; [_loginManager start]; } - (void)_cancel { [_loginManager cancel]; [self.delegate deviceLoginViewControllerDidCancel:self]; } @end