diff options
author | Zsika Phillip <protocol86@users.noreply.github.com> | 2017-09-01 13:38:12 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-01 13:38:12 -0700 |
commit | 36c20d92b7a1f4ac5cb950df61d381ee60be9670 (patch) | |
tree | aff9aa101a52b54ce04357766ef1d3c3c9b380f6 /Firebase | |
parent | 0ce42970fcc94116700c9e6914bc97ce4dd73d00 (diff) |
Add FirAuthUrlPresenter (#222)
* Adds FIRAuthURLPResenter and FIRAuthUIDelegate
Diffstat (limited to 'Firebase')
-rw-r--r-- | Firebase/Auth/FirebaseAuth.podspec | 1 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m | 85 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuthErrorUtils.h | 14 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuthErrorUtils.m | 28 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuthInternalErrors.h | 11 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuthURLPresenter.h | 61 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuthURLPresenter.m | 137 | ||||
-rw-r--r-- | Firebase/Auth/Source/Public/FIRAuthErrors.h | 9 | ||||
-rw-r--r-- | Firebase/Auth/Source/Public/FIRAuthUIDelegate.h | 51 |
9 files changed, 397 insertions, 0 deletions
diff --git a/Firebase/Auth/FirebaseAuth.podspec b/Firebase/Auth/FirebaseAuth.podspec index 3d7eb2f..b213941 100644 --- a/Firebase/Auth/FirebaseAuth.podspec +++ b/Firebase/Auth/FirebaseAuth.podspec @@ -31,6 +31,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel 'Source/**/FIRAuthAPNSTokenType.[mh]', 'Source/**/FIRAuthAPNSToken.[mh]', 'Source/**/FIRPhoneAuthCredential.[mh]', + 'Source/**/FIRAuthDefaultUIDelegate.[mh]', 'Source/**/FIRPhoneAuthProvider.[mh]' s.public_header_files = 'Source/Public/*.h' s.preserve_paths = diff --git a/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m new file mode 100644 index 0000000..118b73c --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m @@ -0,0 +1,85 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Foundation/Foundation.h> + +#import "FIRAuthUIDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthDefaultUIDelegate : NSObject <FIRAuthUIDelegate> +/** @fn defaultUIDelegate + @brief Unavailable. Please use initWithViewController: + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithViewController: + @brief Initializes the instance with a view controller. + @param viewController The view controller as the presenting view controller in @c GOIUIDelegate. + @return The initialized instance. + */ +- (instancetype)initWithViewController:(UIViewController *)viewController NS_DESIGNATED_INITIALIZER; +@end + +@implementation FIRAuthDefaultUIDelegate { + /** @var _viewController + @brief The presenting view controller. + */ + UIViewController *_viewController; +} + +- (instancetype)initWithViewController:(UIViewController *)viewController { + self = [super init]; + if (self) { + _viewController = viewController; + } + return self; +} + +- (void)presentViewController:(UIViewController *)viewControllerToPresent + animated:(BOOL)flag + completion:(nullable void (^)(void))completion { + [_viewController presentViewController:viewControllerToPresent + animated:flag + completion:completion]; +} + +- (void)dismissViewControllerAnimated:(BOOL)flag completion:(nullable void (^)(void))completion { + [_viewController dismissViewControllerAnimated:flag completion:completion]; +} + ++ (id<FIRAuthUIDelegate>)defaultUIDelegate { + UIViewController *topViewController = + [UIApplication sharedApplication].keyWindow.rootViewController; + while (true){ + if (topViewController.presentedViewController) { + topViewController = topViewController.presentedViewController; + } else if ([topViewController isKindOfClass:[UINavigationController class]]) { + UINavigationController *nav = (UINavigationController *)topViewController; + topViewController = nav.topViewController; + } else if ([topViewController isKindOfClass:[UITabBarController class]]) { + UITabBarController *tab = (UITabBarController *)topViewController; + topViewController = tab.selectedViewController; + } else { + break; + } + } + return [[FIRAuthDefaultUIDelegate alloc] initWithViewController:topViewController]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.h b/Firebase/Auth/Source/FIRAuthErrorUtils.h index c2c9171..4fdfe9b 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.h +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.h @@ -451,6 +451,20 @@ NS_ASSUME_NONNULL_BEGIN */ + (NSError *)captchaCheckFailedErrorWithMessage:(nullable NSString *)message; +/** @fn webContextAlreadyPresentedErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeWebContextAlreadyPresented code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)webContextAlreadyPresentedErrorWithMessage:(nullable NSString *)message; + +/** @fn webContextCancelledErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeWebContextCancelledcode. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)webContextCancelledErrorWithMessage:(nullable NSString *)message; + /** @fn keychainErrorWithFunction:status: @brief Constructs an @c NSError with the @c FIRAuthErrorCodeKeychainError code. @param keychainFunction The keychain function which was invoked and yielded an unexpected diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.m b/Firebase/Auth/Source/FIRAuthErrorUtils.m index 0439f81..ad9ad55 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.m +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.m @@ -345,6 +345,18 @@ static NSString *const kFIRAuthErrorMessageAppNotVerified = @"Firebase could not static NSString *const kFIRAuthErrorMessageCaptchaCheckFailed = @"The reCAPTCHA response token " "provided is either invalid, expired or already"; +/** @var kFIRAuthErrorMessageWebContextAlreadyPresented + @brief Message for @c FIRAuthErrorCodeWebContextAlreadyPresented error code. + */ +static NSString *const kFIRAuthErrorMessageWebContextAlreadyPresented = @"User interaction is " + "still ongoing, another view cannot be presented."; + +/** @var kFIRAuthErrorMessageWebContextCancelled + @brief Message for @c FIRAuthErrorCodeWebContextCancelled error code. + */ +static NSString *const kFIRAuthErrorMessageWebContextCancelled = @"The interaction was cancelled " + "by the user."; + /** @var kFIRAuthErrorMessageInternalError @brief Message for @c FIRAuthErrorCodeInternalError error code. */ @@ -455,6 +467,10 @@ static NSString *FIRAuthErrorDescription(FIRAuthErrorCode code) { return kFIRAuthErrorMessageAppNotVerified; case FIRAuthErrorCodeCaptchaCheckFailed: return kFIRAuthErrorMessageCaptchaCheckFailed; + case FIRAuthErrorCodeWebContextAlreadyPresented: + return kFIRAuthErrorMessageWebContextAlreadyPresented; + case FIRAuthErrorCodeWebContextCancelled: + return kFIRAuthErrorMessageWebContextCancelled; } } @@ -562,6 +578,10 @@ static NSString *const FIRAuthErrorCodeString(FIRAuthErrorCode code) { return @"ERROR_APP_NOT_VERIFIED"; case FIRAuthErrorCodeCaptchaCheckFailed: return @"ERROR_CAPTCHA_CHECK_FAILED"; + case FIRAuthErrorCodeWebContextAlreadyPresented: + return @"ERROR_WEB_CONTEXT_ALREADY_PRESENTED"; + case FIRAuthErrorCodeWebContextCancelled: + return @"ERROR_WEB_CONTEXT_CANCELLED"; } } @@ -873,6 +893,14 @@ static NSString *const FIRAuthErrorCodeString(FIRAuthErrorCode code) { return [self errorWithCode:FIRAuthInternalErrorCodeCaptchaCheckFailed message:message]; } ++ (NSError *)webContextAlreadyPresentedErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeWebContextAlreadyPresented message:message]; +} + ++ (NSError *)webContextCancelledErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeWebContextCancelled message:message]; +} + + (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status { NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status]; return [self errorWithCode:FIRAuthInternalErrorCodeKeychainError userInfo:@{ diff --git a/Firebase/Auth/Source/FIRAuthInternalErrors.h b/Firebase/Auth/Source/FIRAuthInternalErrors.h index 724b95c..ffdb068 100644 --- a/Firebase/Auth/Source/FIRAuthInternalErrors.h +++ b/Firebase/Auth/Source/FIRAuthInternalErrors.h @@ -322,6 +322,17 @@ typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) { FIRAuthInternalErrorCodeQuotaExceeded = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeQuotaExceeded, + /** Indicates that an attempt was made to present a new web context while one was already being + presented. + */ + FIRAuthInternalErrorCodeWebContextAlreadyPresented = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebContextAlreadyPresented, + + /** Indicates that the URL presentation was cancelled prematurely by the user. + */ + FIRAuthInternalErrorCodeWebContextCancelled = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebContextCancelled, + // The enum values between 17046 and 17051 are reserved and should NOT be used for new error // codes. diff --git a/Firebase/Auth/Source/FIRAuthURLPresenter.h b/Firebase/Auth/Source/FIRAuthURLPresenter.h new file mode 100644 index 0000000..694b398 --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthURLPresenter.h @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +@protocol FIRAuthUIDelegate; + +/** @typedef FIRAuthURLPresentationCompletion + @brief The type of block invoked when the URLPresentation completes. + @param callbackURL The callback URL if the presentation ends with a matching callback. + @param error The error if the presentation fails to start or ends with an error. + */ +typedef void (^FIRAuthURLPresentationCompletion)(NSURL *_Nullable callbackURL, + NSError *_Nullable error); + +/** @typedef FIRAuthCallbackMatcher + @brief The type of block invoked for checking whether a callback URL matches. + @param callbackURL The callback URL to check for match. + @return Whether or not the specific callback URL matches or not. + */ +typedef BOOL (^FIRAuthURLCallbackMatcher)(NSURL * _Nullable callbackURL); + +@interface FIRAuthURLPresenter : NSObject + +/** @fn presentURL:UIDelegate:callbackMatcher:completion: + @brief Presents an URL to interact with user. + @param URL The URL to present. + @param UIDelegate The UI delegate to present view controller. + @param completion A block to be called either synchronously if the presentation fails to start, + or asynchronously in future on an unspecified thread once the presentation finishes. + */ +- (void)presentURL:(NSURL *)URL + UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate + callbackMatcher:(FIRAuthURLCallbackMatcher)callbackMatcher + completion:(FIRAuthURLPresentationCompletion)completion; + +/** @fn canHandleURL: + @brief Determines if a URL was produced by the currently presented URL. + @param URL The URL to handle. + @return Whether the URL could be handled or not. + */ +- (BOOL)canHandleURL:(NSURL *)URL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRAuthURLPresenter.m b/Firebase/Auth/Source/FIRAuthURLPresenter.m new file mode 100644 index 0000000..90ceed6 --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthURLPresenter.m @@ -0,0 +1,137 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthURLPresenter.h" + +#import "FIRAuthErrorUtils.h" +#import "FIRAuthUIDelegate.h" + +@import SafariServices; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthDefaultUIDelegate : NSObject <FIRAuthUIDelegate> +/** @fn defaultUIDelegate + @brief Returns a default FIRAuthUIDelegate object. + @return The default FIRAuthUIDelegate object. + */ ++ (id<FIRAuthUIDelegate>)defaultUIDelegate; +@end + +@interface FIRAuthURLPresenter () <SFSafariViewControllerDelegate> +@end + +@implementation FIRAuthURLPresenter { + /** @var _callbackMatcher + @brief The callback URL matcher for the current presentation, if one is active. + */ + FIRAuthURLCallbackMatcher _Nullable _callbackMatcher; + + /** @var _safariViewController + @brief The SFSafariViewController used for the current presentation, if any. + */ + SFSafariViewController *_Nullable _safariViewController; + + /** @var _UIDelegate + @brief The UIDelegate used to present the SFSafariViewController. + */ + id<FIRAuthUIDelegate> _UIDelegate; + + /** @var _completion + @brief The completion handler for the current presentaion, if one is active. + @remarks This variable is also used as a flag to indicate a presentation is active. + */ + FIRAuthURLPresentationCompletion _Nullable _completion; +} + +- (void)presentURL:(NSURL *)URL + UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate + callbackMatcher:(FIRAuthURLCallbackMatcher)callbackMatcher + completion:(FIRAuthURLPresentationCompletion)completion { + _callbackMatcher = callbackMatcher; + _completion = completion; + _UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate]; + [self presentWebContextWithController:_UIDelegate URL:URL]; +} + +- (BOOL)canHandleURL:(NSURL *)URL { + if (_callbackMatcher(URL)) { + _callbackMatcher = nil; + [self finishPresentationWithURL:URL error:nil]; + return YES; + } + return NO; +} + +/** @fn presentWebContextWithController:URL: + @brief Presents a SFSafariViewController or WKWebView to display the contents of the URL + provided. + @param controller The controller used to present the SFSafariViewController or WKWebView. + @param URL The URL to display in the SFSafariViewController or WKWebView. + */ +- (void)presentWebContextWithController:(id)controller URL:(NSURL *)URL { + if ([SFSafariViewController class]) { + if (_safariViewController) { + // Unable to start a new presentation on top of modal SFSVC presentation. + _completion(nil, [FIRAuthErrorUtils webContextAlreadyPresentedErrorWithMessage:nil]); + return; + } + SFSafariViewController *safariViewController = [[SFSafariViewController alloc] initWithURL:URL]; + _safariViewController = safariViewController; + _safariViewController.delegate = self; + [controller presentViewController:safariViewController animated:YES completion:nil]; + return; + } +} + +#pragma mark - SFSafariViewControllerDelegate + +- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller { + if (controller == _safariViewController) { + _safariViewController = nil; + //TODO:Ensure that the SFSafariViewController is actually removed from the screen before + //invoking finishPresentationWithURL:error: + [self finishPresentationWithURL:nil + error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]]; + } +} + +#pragma mark - Private methods + +/** @fn finishPresentationWithURL:error: + @brief Finishes the presentation for a given URL, if any. + @param URL The URL to finish presenting. + @param error The error with which to finish presenting, if any. + */ +- (void)finishPresentationWithURL:(nullable NSURL *)URL + error:(nullable NSError *)error { + FIRAuthURLPresentationCompletion completion = _completion; + void (^finishBlock)() = ^() { + completion(URL, nil); + }; + _completion = nil; + if ([SFSafariViewController class]) { + SFSafariViewController *safariViewController = _safariViewController; + _safariViewController = nil; + if (safariViewController) { + [_UIDelegate dismissViewControllerAnimated:YES completion:finishBlock]; + } + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/Public/FIRAuthErrors.h b/Firebase/Auth/Source/Public/FIRAuthErrors.h index 6ab6900..2b7ef96 100644 --- a/Firebase/Auth/Source/Public/FIRAuthErrors.h +++ b/Firebase/Auth/Source/Public/FIRAuthErrors.h @@ -276,6 +276,15 @@ typedef NS_ENUM(NSInteger, FIRAuthErrorCode) { */ FIRAuthErrorCodeCaptchaCheckFailed = 17056, + /** Indicates that an attempt was made to present a new web context while one was already being + presented. + */ + FIRAuthErrorCodeWebContextAlreadyPresented = 17057, + + /** Indicates that the URL presentation was cancelled prematurely by the user. + */ + FIRAuthErrorCodeWebContextCancelled = 17058, + /** Indicates an error occurred while attempting to access the keychain. */ FIRAuthErrorCodeKeychainError = 17995, diff --git a/Firebase/Auth/Source/Public/FIRAuthUIDelegate.h b/Firebase/Auth/Source/Public/FIRAuthUIDelegate.h new file mode 100644 index 0000000..7f9adb0 --- /dev/null +++ b/Firebase/Auth/Source/Public/FIRAuthUIDelegate.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Foundation/Foundation.h> +#import <UIKit/UIKit.h> + +NS_ASSUME_NONNULL_BEGIN + +/** @protocol FIRAuthUIDelegate + @brief A protocol to handle user interface interactions for Firebase Auth. +*/ + +@protocol FIRAuthUIDelegate <NSObject> + +/** @fn presentViewController:animated:completion: + @brief If implemented, this method will be invoked when Firebase Auth needs to display a view + controller. + @param viewControllerToPresent The view controller to be presented. + @param flag Decides whether the view controller presentation should be animated or not. + @param completion The block to execute after the presentation finishes. This block has no return + value and takes no parameters. +*/ +- (void)presentViewController:(UIViewController *)viewControllerToPresent + animated:(BOOL)flag + completion:(void (^ _Nullable)(void))completion; + +/** @fn dismissViewControllerAnimated:completion: + @brief If implemented, this method will be invoked when Firebase Auth needs to display a view + controller. + @param flag Decides whether removing the view controller should be animated or not. + @param completion The block to execute after the presentation finishes. This block has no return + value and takes no parameters. +*/ +- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion; + +@end + +NS_ASSUME_NONNULL_END |