FIRAppDistributionUIService.m 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // Copyright 2019 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "FirebaseAppDistribution/Sources/FIRAppDistributionUIService.h"
  15. #import "FirebaseAppDistribution/Sources/FIRFADLogger.h"
  16. #import "FirebaseAppDistribution/Sources/Public/FirebaseAppDistribution/FIRAppDistribution.h"
  17. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  18. #import <AuthenticationServices/AuthenticationServices.h>
  19. #import <SafariServices/SafariServices.h>
  20. #import <UIKit/UIKit.h>
  21. @implementation FIRAppDistributionUIService
  22. API_AVAILABLE(ios(9.0))
  23. SFSafariViewController *_safariVC;
  24. API_AVAILABLE(ios(12.0))
  25. ASWebAuthenticationSession *_webAuthenticationVC;
  26. API_AVAILABLE(ios(11.0))
  27. SFAuthenticationSession *_safariAuthenticationVC;
  28. - (instancetype)init {
  29. self = [super init];
  30. self.safariHostingViewController = [[UIViewController alloc] init];
  31. return self;
  32. }
  33. + (instancetype)sharedInstance {
  34. static dispatch_once_t once;
  35. static FIRAppDistributionUIService *sharedInstance;
  36. dispatch_once(&once, ^{
  37. sharedInstance = [[FIRAppDistributionUIService alloc] init];
  38. });
  39. return sharedInstance;
  40. }
  41. + (NSString *)encodedAppId {
  42. return [[[FIRApp defaultApp] options].googleAppID stringByReplacingOccurrencesOfString:@":"
  43. withString:@"-"];
  44. }
  45. + (NSError *)getAppDistributionError:(FIRAppDistributionError)appDistributionErrorCode {
  46. NSString *message = appDistributionErrorCode == FIRAppDistributionErrorAuthenticationCancelled
  47. ? @"User cancelled sign-in flow"
  48. : @"Failed to authenticate the user";
  49. NSDictionary *userInfo = @{FIRAppDistributionErrorDetailsKey : message};
  50. return [NSError errorWithDomain:FIRAppDistributionErrorDomain
  51. code:appDistributionErrorCode
  52. userInfo:userInfo];
  53. }
  54. + (NSError *_Nullable)mapErrorToAppDistributionError:(NSError *_Nullable)error {
  55. if (!error) {
  56. return nil;
  57. }
  58. if (@available(iOS 12.0, *)) {
  59. if ([error code] == ASWebAuthenticationSessionErrorCodeCanceledLogin) {
  60. return [self getAppDistributionError:FIRAppDistributionErrorAuthenticationCancelled];
  61. }
  62. } else if (@available(iOS 11.0, *)) {
  63. if ([error code] == SFAuthenticationErrorCanceledLogin) {
  64. return [self getAppDistributionError:FIRAppDistributionErrorAuthenticationCancelled];
  65. }
  66. }
  67. return [self getAppDistributionError:FIRAppDistributionErrorAuthenticationFailure];
  68. }
  69. - (void)appDistributionRegistrationFlow:(NSURL *)URL
  70. withCompletion:(void (^)(NSError *_Nullable error))completion {
  71. NSString *callbackURL =
  72. [NSString stringWithFormat:@"appdistribution-%@", [[self class] encodedAppId]];
  73. FIRFADInfoLog(@"Registration URL: %@", URL);
  74. FIRFADInfoLog(@"Callback URL: %@", callbackURL);
  75. if (@available(iOS 12.0, *)) {
  76. ASWebAuthenticationSession *authenticationVC = [[ASWebAuthenticationSession alloc]
  77. initWithURL:URL
  78. callbackURLScheme:callbackURL
  79. completionHandler:^(NSURL *_Nullable callbackURL, NSError *_Nullable error) {
  80. [self resetUIState];
  81. [self logRegistrationCompletion:error authType:[ASWebAuthenticationSession description]];
  82. NSError *_Nullable appDistributionError =
  83. [[self class] mapErrorToAppDistributionError:error];
  84. completion(appDistributionError);
  85. }];
  86. if (@available(iOS 13.0, *)) {
  87. authenticationVC.presentationContextProvider = self;
  88. }
  89. _webAuthenticationVC = authenticationVC;
  90. [authenticationVC start];
  91. } else if (@available(iOS 11.0, *)) {
  92. _safariAuthenticationVC = [[SFAuthenticationSession alloc]
  93. initWithURL:URL
  94. callbackURLScheme:callbackURL
  95. completionHandler:^(NSURL *_Nullable callbackURL, NSError *_Nullable error) {
  96. [self resetUIState];
  97. [self logRegistrationCompletion:error authType:[SFAuthenticationSession description]];
  98. NSError *_Nullable appDistributionError =
  99. [[self class] mapErrorToAppDistributionError:error];
  100. completion(appDistributionError);
  101. }];
  102. [_safariAuthenticationVC start];
  103. } else if (@available(iOS 9.0, *)) {
  104. SFSafariViewController *safariVC = [[SFSafariViewController alloc] initWithURL:URL];
  105. safariVC.delegate = self;
  106. _safariVC = safariVC;
  107. [self->_safariHostingViewController presentViewController:safariVC animated:YES completion:nil];
  108. self.registrationFlowCompletion = completion;
  109. }
  110. }
  111. - (void)showUIAlert:(UIAlertController *)alertController {
  112. [self initializeUIState];
  113. [self.window.rootViewController presentViewController:alertController
  114. animated:YES
  115. completion:nil];
  116. }
  117. - (void)showUIAlertWithCompletion:(FIRFADUIActionCompletion)completion {
  118. UIAlertController *alert = [UIAlertController
  119. alertControllerWithTitle:NSLocalizedString(
  120. @"Enable new build alerts",
  121. @"Title for App Distribution New Build Alerts UIAlert.")
  122. message:NSLocalizedString(
  123. @"Get in-app alerts when new builds are ready to test.",
  124. @"Description for enabling new build alerts will do.")
  125. preferredStyle:UIAlertControllerStyleAlert];
  126. UIAlertAction *yesButton = [UIAlertAction
  127. actionWithTitle:NSLocalizedString(@"Turn on", @"Button for turning on new build alerts.")
  128. style:UIAlertActionStyleDefault
  129. handler:^(UIAlertAction *action) {
  130. completion(YES);
  131. }];
  132. UIAlertAction *noButton = [UIAlertAction
  133. actionWithTitle:NSLocalizedString(@"Not now",
  134. @"Button for dismissing the new build alerts UIAlert")
  135. style:UIAlertActionStyleDefault
  136. handler:^(UIAlertAction *action) {
  137. [self resetUIState];
  138. completion(NO);
  139. }];
  140. [alert addAction:noButton];
  141. [alert addAction:yesButton];
  142. // Create an empty window + viewController to host the Safari UI.
  143. [self showUIAlert:alert];
  144. }
  145. - (BOOL)application:(UIApplication *)application
  146. openURL:(NSURL *)URL
  147. options:(NSDictionary<NSString *, id> *)options {
  148. if (self.registrationFlowCompletion) {
  149. FIRFADDebugLog(@"Continuing registration flow: %@", [self registrationFlowCompletion]);
  150. [self resetUIState];
  151. if (@available(iOS 9.0, *)) {
  152. [self logRegistrationCompletion:nil authType:[SFSafariViewController description]];
  153. }
  154. self.registrationFlowCompletion(nil);
  155. }
  156. return NO;
  157. }
  158. - (void)logRegistrationCompletion:(NSError *)error authType:(NSString *)authType {
  159. if (error) {
  160. FIRFADErrorLog(@"Failed to complete App Distribution registration flow. Auth type - %@, Error "
  161. @"- %@: %ld. Details - %@",
  162. authType, [error domain], (long)[error code], [error localizedDescription]);
  163. } else {
  164. FIRFADInfoLog(@"App Distribution Registration complete. Auth type - %@", authType);
  165. }
  166. }
  167. - (void)initializeUIState {
  168. if (self.window) {
  169. return;
  170. }
  171. if (@available(iOS 13.0, *)) {
  172. UIWindowScene *foregroundedScene = nil;
  173. for (UIWindowScene *connectedScene in [UIApplication sharedApplication].connectedScenes) {
  174. if (connectedScene.activationState == UISceneActivationStateForegroundActive) {
  175. foregroundedScene = connectedScene;
  176. break;
  177. }
  178. }
  179. if (foregroundedScene) {
  180. self.window = [[UIWindow alloc] initWithWindowScene:foregroundedScene];
  181. } else {
  182. FIRFADInfoLog(@"No foreground scene found.");
  183. self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  184. }
  185. } else {
  186. self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  187. }
  188. self.window.rootViewController = self.safariHostingViewController;
  189. // Place it at the highest level within the stack.
  190. self.window.windowLevel = +CGFLOAT_MAX;
  191. // Run it.
  192. [self.window makeKeyAndVisible];
  193. }
  194. - (void)resetUIState {
  195. if (self.window) {
  196. self.window.rootViewController = nil;
  197. self.window.hidden = YES;
  198. self.window = nil;
  199. }
  200. self.registrationFlowCompletion = nil;
  201. if (@available(iOS 11.0, *)) {
  202. _safariAuthenticationVC = nil;
  203. } else if (@available(iOS 12.0, *)) {
  204. _webAuthenticationVC = nil;
  205. } else if (@available(iOS 9.0, *)) {
  206. _safariVC = nil;
  207. }
  208. }
  209. - (void)safariViewControllerDidFinish:(SFSafariViewController *)controller NS_AVAILABLE_IOS(9.0) {
  210. NSError *error =
  211. [[self class] getAppDistributionError:FIRAppDistributionErrorAuthenticationCancelled];
  212. [self logRegistrationCompletion:error authType:[SFSafariViewController description]];
  213. if (self.registrationFlowCompletion) {
  214. self.registrationFlowCompletion(error);
  215. }
  216. [self resetUIState];
  217. }
  218. - (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:
  219. (ASWebAuthenticationSession *)session API_AVAILABLE(ios(13.0)) {
  220. return self.safariHostingViewController.view.window;
  221. }
  222. @end