1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "ExpoKit.h" 4#import "EXViewController.h" 5#import "EXAnalytics.h" 6#import "EXBuildConstants.h" 7#import "EXEnvironment.h" 8#import "EXGoogleAuthManager.h" 9#import "EXKernel.h" 10#import "EXKernelUtil.h" 11#import "EXKernelLinkingManager.h" 12#import "EXReactAppExceptionHandler.h" 13#import "EXRemoteNotificationManager.h" 14#import "EXBranchManager.h" 15 16#import <Crashlytics/Crashlytics.h> 17#import <GoogleMaps/GoogleMaps.h> 18 19NSString * const EXAppDidRegisterForRemoteNotificationsNotification = @"kEXAppDidRegisterForRemoteNotificationsNotification"; 20NSString * const EXAppDidRegisterUserNotificationSettingsNotification = @"kEXAppDidRegisterUserNotificationSettingsNotification"; 21 22@interface ExpoKit () <CrashlyticsDelegate> 23{ 24 Class _rootViewControllerClass; 25} 26 27@property (nonatomic, nullable, strong) EXViewController *rootViewController; 28@property (nonatomic, strong) NSDictionary *launchOptions; 29 30@end 31 32@implementation ExpoKit 33 34+ (nonnull instancetype)sharedInstance 35{ 36 static ExpoKit *theExpoKit = nil; 37 static dispatch_once_t once; 38 dispatch_once(&once, ^{ 39 if (!theExpoKit) { 40 theExpoKit = [[ExpoKit alloc] init]; 41 } 42 }); 43 return theExpoKit; 44} 45 46- (instancetype)init 47{ 48 if (self = [super init]) { 49 _rootViewControllerClass = [EXViewController class]; 50 [self _initDefaultKeys]; 51 } 52 return self; 53} 54 55- (void)dealloc 56{ 57 [[NSNotificationCenter defaultCenter] removeObserver:self]; 58} 59 60- (void)registerRootViewControllerClass:(Class)rootViewControllerClass 61{ 62 NSAssert([rootViewControllerClass isSubclassOfClass:[EXViewController class]], @"ExpoKit root view controller class must subclass EXViewController."); 63 _rootViewControllerClass = rootViewControllerClass; 64} 65 66- (EXViewController *)rootViewController 67{ 68 if (!_rootViewController) { 69 _rootViewController = [[_rootViewControllerClass alloc] init]; 70 _rootViewController.delegate = [EXKernel sharedInstance]; 71 } 72 return _rootViewController; 73} 74 75- (UIViewController *)currentViewController 76{ 77 EXViewController *rootViewController = [self rootViewController]; 78 UIViewController *controller = [rootViewController contentViewController]; 79 while (controller.presentedViewController != nil) { 80 controller = controller.presentedViewController; 81 } 82 return controller; 83} 84 85#pragma mark - misc AppDelegate hooks 86 87- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 88{ 89 [DDLog addLogger:[DDOSLogger sharedInstance]]; 90 91 92 RCTSetFatalHandler(handleFatalReactError); 93 94 // init analytics 95 [EXAnalytics sharedInstance]; 96 97 NSString *standaloneGMSKey = [[NSBundle mainBundle].infoDictionary objectForKey:@"GMSApiKey"]; 98 if (standaloneGMSKey && standaloneGMSKey.length) { 99 [GMSServices provideAPIKey:standaloneGMSKey]; 100 } else { 101 if (_applicationKeys[@"GOOGLE_MAPS_IOS_API_KEY"]) {// we may define this as empty 102 if ([_applicationKeys[@"GOOGLE_MAPS_IOS_API_KEY"] length]) { 103 [GMSServices provideAPIKey:_applicationKeys[@"GOOGLE_MAPS_IOS_API_KEY"]]; 104 } 105 } 106 } 107 108 [UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) [EXKernel sharedInstance].serviceRegistry.notificationsManager; 109 // This is safe to call; if the app doesn't have permission to display user-facing notifications 110 // then registering for a push token is a no-op 111 [[EXKernel sharedInstance].serviceRegistry.remoteNotificationManager registerForRemoteNotifications]; 112 [[EXKernel sharedInstance].serviceRegistry.branchManager application:application didFinishLaunchingWithOptions:launchOptions]; 113 _launchOptions = launchOptions; 114} 115 116#pragma mark - Crash handling 117 118- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report 119{ 120 // set a persistent flag because we may not get a chance to take any action until a future execution of the app. 121 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kEXKernelClearJSCacheUserDefaultsKey]; 122 123 // block to ensure we save this key (in case the app crashes again) 124 [[NSUserDefaults standardUserDefaults] synchronize]; 125} 126 127#pragma mark - APNS hooks 128 129- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token 130{ 131 [[EXKernel sharedInstance].serviceRegistry.remoteNotificationManager registerAPNSToken:token registrationError:nil]; 132 [[NSNotificationCenter defaultCenter] postNotificationName:EXAppDidRegisterForRemoteNotificationsNotification object:nil userInfo:@{ @"token": token }]; 133} 134 135- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err 136{ 137 DDLogWarn(@"Failed to register for remote notifs: %@", err); 138 [[EXKernel sharedInstance].serviceRegistry.remoteNotificationManager registerAPNSToken:nil registrationError:err]; 139 140 // Post this even in the failure case -- up to subscribers to subsequently read the system permission state 141 [[NSNotificationCenter defaultCenter] postNotificationName:EXAppDidRegisterForRemoteNotificationsNotification object:nil]; 142} 143 144- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 145fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { 146 147 // Here background task execution should go. 148 149 completionHandler(UIBackgroundFetchResultNoData); 150} 151 152// TODO: Remove once SDK31 is phased out 153#pragma clang diagnostic push 154#pragma clang diagnostic ignored "-Wdeprecated-declarations" 155- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings *)notificationSettings 156{ 157 [[NSNotificationCenter defaultCenter] postNotificationName:EXAppDidRegisterUserNotificationSettingsNotification object:nil]; 158} 159#pragma clang diagnostic pop 160 161#pragma mark - deep linking hooks 162 163- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation 164{ 165 if ([[EXKernel sharedInstance].serviceRegistry.googleAuthManager 166 application:application openURL:url sourceApplication:sourceApplication annotation:annotation]) { 167 return YES; 168 } 169 170 if ([[EXKernel sharedInstance].serviceRegistry.branchManager 171 application:application 172 openURL:url 173 sourceApplication:sourceApplication 174 annotation:annotation]) { 175 return YES; 176 } 177 178 return [EXKernelLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; 179} 180 181- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler 182{ 183 if ([[EXKernel sharedInstance].serviceRegistry.branchManager 184 application:application 185 continueUserActivity:userActivity 186 restorationHandler:restorationHandler]) { 187 return YES; 188 } 189 190 if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { 191 NSURL *webpageURL = userActivity.webpageURL; 192 if ([EXEnvironment sharedEnvironment].isDetached) { 193 return [EXKernelLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 194 } else { 195 NSString *path = [webpageURL path]; 196 197 // Filter out URLs that don't match experience URLs since the AASA pattern's grammar is not as 198 // expressive as we'd like and matches profile URLs too 199 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^/@[a-z0-9_-]+/.+$" 200 options:NSRegularExpressionCaseInsensitive 201 error:nil]; 202 NSUInteger matchCount = [regex numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]; 203 204 if (matchCount > 0) { 205 [EXKernelLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 206 return YES; 207 } else { 208 [application openURL:webpageURL options:@{} completionHandler:nil]; 209 return YES; 210 } 211 } 212 } 213 214 return NO; 215} 216 217#pragma mark - internal 218 219- (void)_initDefaultKeys 220{ 221 // these are provided in the expo/expo open source repo as defaults; they can all be overridden by setting 222 // the `applicationKeys` property on ExpoKit. 223 if ([EXBuildConstants sharedInstance].defaultApiKeys) { 224 self.applicationKeys = [EXBuildConstants sharedInstance].defaultApiKeys; 225 } 226} 227 228@end 229