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 = [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#pragma mark - deep linking hooks 153 154- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation 155{ 156 if ([[EXKernel sharedInstance].serviceRegistry.googleAuthManager 157 application:application openURL:url sourceApplication:sourceApplication annotation:annotation]) { 158 return YES; 159 } 160 161 if ([[EXKernel sharedInstance].serviceRegistry.branchManager 162 application:application 163 openURL:url 164 sourceApplication:sourceApplication 165 annotation:annotation]) { 166 return YES; 167 } 168 169 return [EXKernelLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; 170} 171 172- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler 173{ 174 if ([[EXKernel sharedInstance].serviceRegistry.branchManager 175 application:application 176 continueUserActivity:userActivity 177 restorationHandler:restorationHandler]) { 178 return YES; 179 } 180 181 if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { 182 NSURL *webpageURL = userActivity.webpageURL; 183 if ([EXEnvironment sharedEnvironment].isDetached) { 184 return [EXKernelLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 185 } else { 186 NSString *path = [webpageURL path]; 187 188 // Filter out URLs that don't match experience URLs since the AASA pattern's grammar is not as 189 // expressive as we'd like and matches profile URLs too 190 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^/@[a-z0-9_-]+/.+$" 191 options:NSRegularExpressionCaseInsensitive 192 error:nil]; 193 NSUInteger matchCount = [regex numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]; 194 195 if (matchCount > 0) { 196 [EXKernelLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 197 return YES; 198 } else { 199 [application openURL:webpageURL]; 200 return YES; 201 } 202 } 203 } 204 205 return NO; 206} 207 208#pragma mark - internal 209 210- (void)_initDefaultKeys 211{ 212 // these are provided in the expo/expo open source repo as defaults; they can all be overridden by setting 213 // the `applicationKeys` property on ExpoKit. 214 if ([EXBuildConstants sharedInstance].defaultApiKeys) { 215 self.applicationKeys = [EXBuildConstants sharedInstance].defaultApiKeys; 216 } 217} 218 219@end 220