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