aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase
diff options
context:
space:
mode:
authorGravatar Zsika Phillip <protocol86@users.noreply.github.com>2017-09-01 13:38:12 -0700
committerGravatar GitHub <noreply@github.com>2017-09-01 13:38:12 -0700
commit36c20d92b7a1f4ac5cb950df61d381ee60be9670 (patch)
treeaff9aa101a52b54ce04357766ef1d3c3c9b380f6 /Firebase
parent0ce42970fcc94116700c9e6914bc97ce4dd73d00 (diff)
Add FirAuthUrlPresenter (#222)
* Adds FIRAuthURLPResenter and FIRAuthUIDelegate
Diffstat (limited to 'Firebase')
-rw-r--r--Firebase/Auth/FirebaseAuth.podspec1
-rw-r--r--Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m85
-rw-r--r--Firebase/Auth/Source/FIRAuthErrorUtils.h14
-rw-r--r--Firebase/Auth/Source/FIRAuthErrorUtils.m28
-rw-r--r--Firebase/Auth/Source/FIRAuthInternalErrors.h11
-rw-r--r--Firebase/Auth/Source/FIRAuthURLPresenter.h61
-rw-r--r--Firebase/Auth/Source/FIRAuthURLPresenter.m137
-rw-r--r--Firebase/Auth/Source/Public/FIRAuthErrors.h9
-rw-r--r--Firebase/Auth/Source/Public/FIRAuthUIDelegate.h51
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