FIRAuthURLPresenter.m 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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_OSX && !TARGET_OS_TV
  18. #import "FIRAuthURLPresenter.h"
  19. #import <SafariServices/SafariServices.h>
  20. #import "FIRAuthDefaultUIDelegate.h"
  21. #import "FIRAuthErrorUtils.h"
  22. #import "FIRAuthGlobalWorkQueue.h"
  23. #import "FIRAuthUIDelegate.h"
  24. #import "FIRAuthWebViewController.h"
  25. NS_ASSUME_NONNULL_BEGIN
  26. @interface FIRAuthURLPresenter () <SFSafariViewControllerDelegate,
  27. FIRAuthWebViewControllerDelegate>
  28. @end
  29. // Disable unguarded availability warnings because SFSafariViewController is been used throughout
  30. // the code, including as an iVar, which cannot be simply excluded by @available check.
  31. #pragma clang diagnostic push
  32. #pragma clang diagnostic ignored "-Wunguarded-availability"
  33. @implementation FIRAuthURLPresenter {
  34. /** @var _isPresenting
  35. @brief Whether or not some web-based content is being presented.
  36. */
  37. BOOL _isPresenting;
  38. /** @var _callbackMatcher
  39. @brief The callback URL matcher for the current presentation, if one is active.
  40. */
  41. FIRAuthURLCallbackMatcher _Nullable _callbackMatcher;
  42. /** @var _safariViewController
  43. @brief The SFSafariViewController used for the current presentation, if any.
  44. */
  45. SFSafariViewController *_Nullable _safariViewController;
  46. /** @var _webViewController
  47. @brief The FIRAuthWebViewController used for the current presentation, if any.
  48. */
  49. FIRAuthWebViewController *_Nullable _webViewController;
  50. /** @var _UIDelegate
  51. @brief The UIDelegate used to present the SFSafariViewController.
  52. */
  53. id<FIRAuthUIDelegate> _UIDelegate;
  54. /** @var _completion
  55. @brief The completion handler for the current presentaion, if one is active.
  56. @remarks This variable is also used as a flag to indicate a presentation is active.
  57. */
  58. FIRAuthURLPresentationCompletion _Nullable _completion;
  59. }
  60. - (void)presentURL:(NSURL *)URL
  61. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  62. callbackMatcher:(FIRAuthURLCallbackMatcher)callbackMatcher
  63. completion:(FIRAuthURLPresentationCompletion)completion {
  64. if (_isPresenting) {
  65. // Unable to start a new presentation on top of another.
  66. _completion(nil, [FIRAuthErrorUtils webContextAlreadyPresentedErrorWithMessage:nil]);
  67. return;
  68. }
  69. _isPresenting = YES;
  70. _callbackMatcher = callbackMatcher;
  71. _completion = completion;
  72. dispatch_async(dispatch_get_main_queue(), ^() {
  73. self->_UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate];
  74. if ([SFSafariViewController class]) {
  75. self->_safariViewController = [[SFSafariViewController alloc] initWithURL:URL];
  76. self->_safariViewController.delegate = self;
  77. [self->_UIDelegate presentViewController:self->_safariViewController
  78. animated:YES
  79. completion:nil];
  80. return;
  81. } else {
  82. self->_webViewController = [[FIRAuthWebViewController alloc] initWithURL:URL delegate:self];
  83. UINavigationController *navController =
  84. [[UINavigationController alloc] initWithRootViewController:self->_webViewController];
  85. [self->_UIDelegate presentViewController:navController animated:YES completion:nil];
  86. }
  87. });
  88. }
  89. - (BOOL)canHandleURL:(NSURL *)URL {
  90. if (_isPresenting && _callbackMatcher && _callbackMatcher(URL)) {
  91. [self finishPresentationWithURL:URL error:nil];
  92. return YES;
  93. }
  94. return NO;
  95. }
  96. #pragma mark - SFSafariViewControllerDelegate
  97. - (void)safariViewControllerDidFinish:(SFSafariViewController *)controller {
  98. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  99. if (controller == self->_safariViewController) {
  100. self->_safariViewController = nil;
  101. //TODO:Ensure that the SFSafariViewController is actually removed from the screen before
  102. //invoking finishPresentationWithURL:error:
  103. [self finishPresentationWithURL:nil
  104. error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
  105. }
  106. });
  107. }
  108. #pragma mark - FIRAuthwebViewControllerDelegate
  109. - (BOOL)webViewController:(FIRAuthWebViewController *)webViewController canHandleURL:(NSURL *)URL {
  110. __block BOOL result = NO;
  111. dispatch_sync(FIRAuthGlobalWorkQueue(), ^() {
  112. if (webViewController == self->_webViewController) {
  113. result = [self canHandleURL:URL];
  114. }
  115. });
  116. return result;
  117. }
  118. - (void)webViewControllerDidCancel:(FIRAuthWebViewController *)webViewController {
  119. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  120. if (webViewController == self->_webViewController) {
  121. [self finishPresentationWithURL:nil
  122. error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
  123. }
  124. });
  125. }
  126. - (void)webViewController:(FIRAuthWebViewController *)webViewController
  127. didFailWithError:(NSError *)error {
  128. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  129. if (webViewController == self->_webViewController) {
  130. [self finishPresentationWithURL:nil error:error];
  131. }
  132. });
  133. }
  134. #pragma mark - Private methods
  135. /** @fn finishPresentationWithURL:error:
  136. @brief Finishes the presentation for a given URL, if any.
  137. @param URL The URL to finish presenting.
  138. @param error The error with which to finish presenting, if any.
  139. */
  140. - (void)finishPresentationWithURL:(nullable NSURL *)URL
  141. error:(nullable NSError *)error {
  142. _callbackMatcher = nil;
  143. id<FIRAuthUIDelegate> UIDelegate = _UIDelegate;
  144. _UIDelegate = nil;
  145. FIRAuthURLPresentationCompletion completion = _completion;
  146. _completion = nil;
  147. void (^finishBlock)(void) = ^() {
  148. self->_isPresenting = NO;
  149. completion(URL, error);
  150. };
  151. SFSafariViewController *safariViewController = _safariViewController;
  152. _safariViewController = nil;
  153. FIRAuthWebViewController *webViewController = _webViewController;
  154. _webViewController = nil;
  155. if (safariViewController || webViewController) {
  156. dispatch_async(dispatch_get_main_queue(), ^() {
  157. [UIDelegate dismissViewControllerAnimated:YES completion:^() {
  158. dispatch_async(FIRAuthGlobalWorkQueue(), finishBlock);
  159. }];
  160. });
  161. } else {
  162. finishBlock();
  163. }
  164. }
  165. #pragma clang diagnostic pop // ignored "-Wunguarded-availability"
  166. @end
  167. NS_ASSUME_NONNULL_END
  168. #endif