You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
6.6 KiB

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