FIRAuthURLPresenter.m 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include <TargetConditionals.h>
  17. #if TARGET_OS_IOS
  18. #import <FirebaseAuth/FIRAuthUIDelegate.h>
  19. #import <SafariServices/SafariServices.h>
  20. #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
  21. #import "FirebaseAuth/Sources/Utilities/FIRAuthDefaultUIDelegate.h"
  22. #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
  23. #import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
  24. #import "FirebaseAuth/Sources/Utilities/FIRAuthWebViewController.h"
  25. NS_ASSUME_NONNULL_BEGIN
  26. @interface FIRAuthURLPresenter () <SFSafariViewControllerDelegate, FIRAuthWebViewControllerDelegate>
  27. @end
  28. // Disable unguarded availability warnings because SFSafariViewController is been used throughout
  29. // the code, including as an iVar, which cannot be simply excluded by @available check.
  30. #pragma clang diagnostic push
  31. #pragma clang diagnostic ignored "-Wunguarded-availability"
  32. @implementation FIRAuthURLPresenter {
  33. /** @var _isPresenting
  34. @brief Whether or not some web-based content is being presented.
  35. */
  36. BOOL _isPresenting;
  37. /** @var _callbackMatcher
  38. @brief The callback URL matcher for the current presentation, if one is active.
  39. */
  40. FIRAuthURLCallbackMatcher _Nullable _callbackMatcher;
  41. /** @var _safariViewController
  42. @brief The SFSafariViewController used for the current presentation, if any.
  43. */
  44. SFSafariViewController *_Nullable _safariViewController;
  45. /** @var _webViewController
  46. @brief The FIRAuthWebViewController used for the current presentation, if any.
  47. */
  48. FIRAuthWebViewController *_Nullable _webViewController;
  49. /** @var _UIDelegate
  50. @brief The UIDelegate used to present the SFSafariViewController.
  51. */
  52. id<FIRAuthUIDelegate> _UIDelegate;
  53. /** @var _completion
  54. @brief The completion handler for the current presentaion, if one is active.
  55. @remarks This variable is also used as a flag to indicate a presentation is active.
  56. */
  57. FIRAuthURLPresentationCompletion _Nullable _completion;
  58. }
  59. - (void)presentURL:(NSURL *)URL
  60. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  61. callbackMatcher:(FIRAuthURLCallbackMatcher)callbackMatcher
  62. completion:(FIRAuthURLPresentationCompletion)completion {
  63. if (_isPresenting) {
  64. // Unable to start a new presentation on top of another.
  65. _completion(nil, [FIRAuthErrorUtils webContextAlreadyPresentedErrorWithMessage:nil]);
  66. return;
  67. }
  68. _isPresenting = YES;
  69. _callbackMatcher = callbackMatcher;
  70. _completion = completion;
  71. dispatch_async(dispatch_get_main_queue(), ^() {
  72. self->_UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate];
  73. if ([SFSafariViewController class]) {
  74. self->_safariViewController = [[SFSafariViewController alloc] initWithURL:URL];
  75. self->_safariViewController.delegate = self;
  76. [self->_UIDelegate presentViewController:self->_safariViewController
  77. animated:YES
  78. completion:nil];
  79. return;
  80. } else {
  81. self->_webViewController = [[FIRAuthWebViewController alloc] initWithURL:URL delegate:self];
  82. UINavigationController *navController =
  83. [[UINavigationController alloc] initWithRootViewController:self->_webViewController];
  84. [self->_UIDelegate presentViewController:navController animated:YES completion:nil];
  85. }
  86. });
  87. }
  88. - (BOOL)canHandleURL:(NSURL *)URL {
  89. if (_isPresenting && _callbackMatcher && _callbackMatcher(URL)) {
  90. [self finishPresentationWithURL:URL error:nil];
  91. return YES;
  92. }
  93. return NO;
  94. }
  95. #pragma mark - SFSafariViewControllerDelegate
  96. - (void)safariViewControllerDidFinish:(SFSafariViewController *)controller {
  97. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  98. if (controller == self->_safariViewController) {
  99. self->_safariViewController = nil;
  100. // TODO:Ensure that the SFSafariViewController is actually removed from the screen before
  101. // invoking finishPresentationWithURL:error:
  102. [self finishPresentationWithURL:nil
  103. error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
  104. }
  105. });
  106. }
  107. #pragma mark - FIRAuthwebViewControllerDelegate
  108. - (BOOL)webViewController:(FIRAuthWebViewController *)webViewController canHandleURL:(NSURL *)URL {
  109. __block BOOL result = NO;
  110. dispatch_sync(FIRAuthGlobalWorkQueue(), ^() {
  111. if (webViewController == self->_webViewController) {
  112. result = [self canHandleURL:URL];
  113. }
  114. });
  115. return result;
  116. }
  117. - (void)webViewControllerDidCancel:(FIRAuthWebViewController *)webViewController {
  118. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  119. if (webViewController == self->_webViewController) {
  120. [self finishPresentationWithURL:nil
  121. error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
  122. }
  123. });
  124. }
  125. - (void)webViewController:(FIRAuthWebViewController *)webViewController
  126. didFailWithError:(NSError *)error {
  127. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  128. if (webViewController == self->_webViewController) {
  129. [self finishPresentationWithURL:nil error:error];
  130. }
  131. });
  132. }
  133. #pragma mark - Private methods
  134. /** @fn finishPresentationWithURL:error:
  135. @brief Finishes the presentation for a given URL, if any.
  136. @param URL The URL to finish presenting.
  137. @param error The error with which to finish presenting, if any.
  138. */
  139. - (void)finishPresentationWithURL:(nullable NSURL *)URL error:(nullable NSError *)error {
  140. _callbackMatcher = nil;
  141. id<FIRAuthUIDelegate> UIDelegate = _UIDelegate;
  142. _UIDelegate = nil;
  143. FIRAuthURLPresentationCompletion completion = _completion;
  144. _completion = nil;
  145. void (^finishBlock)(void) = ^() {
  146. self->_isPresenting = NO;
  147. completion(URL, error);
  148. };
  149. SFSafariViewController *safariViewController = _safariViewController;
  150. _safariViewController = nil;
  151. FIRAuthWebViewController *webViewController = _webViewController;
  152. _webViewController = nil;
  153. if (safariViewController || webViewController) {
  154. dispatch_async(dispatch_get_main_queue(), ^() {
  155. [UIDelegate dismissViewControllerAnimated:YES
  156. completion:^() {
  157. dispatch_async(FIRAuthGlobalWorkQueue(), finishBlock);
  158. }];
  159. });
  160. } else {
  161. finishBlock();
  162. }
  163. }
  164. #pragma clang diagnostic pop // ignored "-Wunguarded-availability"
  165. @end
  166. NS_ASSUME_NONNULL_END
  167. #endif